@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,227 @@
|
|
|
1
|
+
import { runCli } from '../services/ai-usage/spawn'
|
|
2
|
+
|
|
3
|
+
export type PrCheckState = 'pass' | 'fail' | 'pending' | 'skipping' | 'cancel'
|
|
4
|
+
|
|
5
|
+
export interface PrCheck {
|
|
6
|
+
name: string
|
|
7
|
+
workflow: string
|
|
8
|
+
state: PrCheckState
|
|
9
|
+
url: string
|
|
10
|
+
durationMs: number | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PrSummary {
|
|
14
|
+
number: number
|
|
15
|
+
title: string
|
|
16
|
+
body: string
|
|
17
|
+
state: string
|
|
18
|
+
isDraft: boolean
|
|
19
|
+
base: string
|
|
20
|
+
head: string
|
|
21
|
+
reviewDecision: string
|
|
22
|
+
/** MERGEABLE | CONFLICTING | UNKNOWN */
|
|
23
|
+
mergeable: string
|
|
24
|
+
/** CLEAN | BLOCKED | BEHIND | UNSTABLE | DIRTY | DRAFT | HAS_HOOKS | UNKNOWN */
|
|
25
|
+
mergeStateStatus: string
|
|
26
|
+
additions: number
|
|
27
|
+
deletions: number
|
|
28
|
+
changedFiles: number
|
|
29
|
+
url: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type PrStatusResult =
|
|
33
|
+
| { kind: 'ok'; pr: PrSummary; checks: PrCheck[] }
|
|
34
|
+
| { kind: 'no-pr' }
|
|
35
|
+
| { kind: 'no-gh' }
|
|
36
|
+
| { kind: 'error'; message: string }
|
|
37
|
+
|
|
38
|
+
const PR_VIEW_FIELDS = [
|
|
39
|
+
'number',
|
|
40
|
+
'title',
|
|
41
|
+
'body',
|
|
42
|
+
'state',
|
|
43
|
+
'isDraft',
|
|
44
|
+
'url',
|
|
45
|
+
'baseRefName',
|
|
46
|
+
'headRefName',
|
|
47
|
+
'reviewDecision',
|
|
48
|
+
'mergeable',
|
|
49
|
+
'mergeStateStatus',
|
|
50
|
+
'additions',
|
|
51
|
+
'deletions',
|
|
52
|
+
'changedFiles',
|
|
53
|
+
'statusCheckRollup',
|
|
54
|
+
].join(',')
|
|
55
|
+
|
|
56
|
+
const NOT_AN_ERROR = [
|
|
57
|
+
'no pull requests found',
|
|
58
|
+
'no git remotes',
|
|
59
|
+
'not a git repository',
|
|
60
|
+
'none of the git remotes',
|
|
61
|
+
'no default remote',
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
function str(value: unknown): string {
|
|
65
|
+
return typeof value === 'string' ? value : ''
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function num(value: unknown): number {
|
|
69
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function duration(startedAt: unknown, completedAt: unknown): number | null {
|
|
73
|
+
const start = Date.parse(str(startedAt))
|
|
74
|
+
const end = Date.parse(str(completedAt))
|
|
75
|
+
if (Number.isNaN(start) || Number.isNaN(end) || end < start) return null
|
|
76
|
+
return end - start
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// `gh` documents these buckets for `pr checks --json bucket`; we derive the same
|
|
80
|
+
// classification from the raw rollup so a single `pr view` call covers both the
|
|
81
|
+
// summary and the checks.
|
|
82
|
+
function checkRunState(status: string, conclusion: string): PrCheckState {
|
|
83
|
+
if (status !== 'COMPLETED') return 'pending'
|
|
84
|
+
if (conclusion === 'SUCCESS' || conclusion === 'NEUTRAL') return 'pass'
|
|
85
|
+
if (conclusion === 'SKIPPED') return 'skipping'
|
|
86
|
+
if (conclusion === 'CANCELLED') return 'cancel'
|
|
87
|
+
return 'fail'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function statusContextState(state: string): PrCheckState {
|
|
91
|
+
if (state === 'SUCCESS') return 'pass'
|
|
92
|
+
if (state === 'PENDING' || state === 'EXPECTED') return 'pending'
|
|
93
|
+
return 'fail'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function toCheck(raw: unknown): PrCheck | null {
|
|
97
|
+
if (typeof raw !== 'object' || raw === null) return null
|
|
98
|
+
const entry = raw as Record<string, unknown>
|
|
99
|
+
if (entry.__typename === 'StatusContext') {
|
|
100
|
+
const name = str(entry.context)
|
|
101
|
+
if (name === '') return null
|
|
102
|
+
return {
|
|
103
|
+
durationMs: null,
|
|
104
|
+
name,
|
|
105
|
+
state: statusContextState(str(entry.state)),
|
|
106
|
+
url: str(entry.targetUrl),
|
|
107
|
+
workflow: '',
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const name = str(entry.name)
|
|
111
|
+
if (name === '') return null
|
|
112
|
+
return {
|
|
113
|
+
durationMs: duration(entry.startedAt, entry.completedAt),
|
|
114
|
+
name,
|
|
115
|
+
state: checkRunState(str(entry.status), str(entry.conclusion)),
|
|
116
|
+
url: str(entry.detailsUrl),
|
|
117
|
+
workflow: str(entry.workflowName),
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function parsePrView(raw: unknown): PrStatusResult {
|
|
122
|
+
if (typeof raw !== 'object' || raw === null) return { kind: 'no-pr' }
|
|
123
|
+
const pr = raw as Record<string, unknown>
|
|
124
|
+
if (typeof pr.number !== 'number') return { kind: 'no-pr' }
|
|
125
|
+
const rollup = Array.isArray(pr.statusCheckRollup) ? pr.statusCheckRollup : []
|
|
126
|
+
return {
|
|
127
|
+
checks: rollup.map(toCheck).filter((c): c is PrCheck => c !== null),
|
|
128
|
+
kind: 'ok',
|
|
129
|
+
pr: {
|
|
130
|
+
additions: num(pr.additions),
|
|
131
|
+
base: str(pr.baseRefName),
|
|
132
|
+
body: str(pr.body).trim(),
|
|
133
|
+
changedFiles: num(pr.changedFiles),
|
|
134
|
+
deletions: num(pr.deletions),
|
|
135
|
+
head: str(pr.headRefName),
|
|
136
|
+
isDraft: pr.isDraft === true,
|
|
137
|
+
mergeable: str(pr.mergeable),
|
|
138
|
+
mergeStateStatus: str(pr.mergeStateStatus),
|
|
139
|
+
number: pr.number,
|
|
140
|
+
reviewDecision: str(pr.reviewDecision),
|
|
141
|
+
state: str(pr.state),
|
|
142
|
+
title: str(pr.title),
|
|
143
|
+
url: str(pr.url),
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type PrAction = 'merge' | null
|
|
149
|
+
|
|
150
|
+
export interface PrActionState {
|
|
151
|
+
label: string
|
|
152
|
+
action: PrAction
|
|
153
|
+
tone: 'ok' | 'blocked' | 'neutral'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The headline GitHub puts on the merge box, and the one action worth wiring to
|
|
158
|
+
* it. Order matters: a terminal state beats everything, then a hard blocker
|
|
159
|
+
* (conflicts, draft), then whatever the checks are doing. Anything we can't
|
|
160
|
+
* offer an action for still gets an honest label rather than a dead button.
|
|
161
|
+
*/
|
|
162
|
+
export function prActionState(pr: PrSummary, checks: PrCheck[]): PrActionState {
|
|
163
|
+
if (pr.state === 'MERGED') return { action: null, label: 'Merged', tone: 'neutral' }
|
|
164
|
+
if (pr.state === 'CLOSED') return { action: null, label: 'Closed', tone: 'blocked' }
|
|
165
|
+
if (pr.isDraft) return { action: null, label: 'Draft', tone: 'neutral' }
|
|
166
|
+
if (pr.mergeable === 'CONFLICTING') {
|
|
167
|
+
return { action: null, label: 'Merge conflicts', tone: 'blocked' }
|
|
168
|
+
}
|
|
169
|
+
if (checks.some((c) => c.state === 'pending')) {
|
|
170
|
+
return { action: null, label: 'Checks running', tone: 'neutral' }
|
|
171
|
+
}
|
|
172
|
+
if (pr.mergeStateStatus === 'BLOCKED') return { action: null, label: 'Blocked', tone: 'blocked' }
|
|
173
|
+
if (pr.mergeStateStatus === 'BEHIND')
|
|
174
|
+
return { action: null, label: 'Out of date', tone: 'blocked' }
|
|
175
|
+
// UNSTABLE means a non-required check failed; GitHub still lets you merge.
|
|
176
|
+
if (pr.mergeStateStatus === 'UNSTABLE') {
|
|
177
|
+
return { action: 'merge', label: 'Checks failing', tone: 'blocked' }
|
|
178
|
+
}
|
|
179
|
+
if (pr.mergeStateStatus === 'CLEAN') {
|
|
180
|
+
return { action: 'merge', label: 'Ready to merge', tone: 'ok' }
|
|
181
|
+
}
|
|
182
|
+
return { action: null, label: 'Checking…', tone: 'neutral' }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface ClampedBody {
|
|
186
|
+
text: string
|
|
187
|
+
truncated: boolean
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* A PR body is a whole document; a bar widget gets a preview of it. Clamping on
|
|
192
|
+
* lines alone breaks on a wall-of-text paragraph and clamping on characters
|
|
193
|
+
* alone breaks on a bullet list, so whichever limit bites first wins.
|
|
194
|
+
*/
|
|
195
|
+
export function clampPrBody(body: string, maxLines = 5, maxChars = 260): ClampedBody {
|
|
196
|
+
const full = body.trimEnd()
|
|
197
|
+
const lines = full.split('\n')
|
|
198
|
+
let text = lines.slice(0, maxLines).join('\n')
|
|
199
|
+
if (text.length > maxChars) {
|
|
200
|
+
const space = text.lastIndexOf(' ', maxChars)
|
|
201
|
+
// Only respect a word boundary that isn't absurdly early, else hard-cut.
|
|
202
|
+
text = text.slice(0, space > maxChars / 2 ? space : maxChars)
|
|
203
|
+
}
|
|
204
|
+
text = text.trimEnd()
|
|
205
|
+
return { text, truncated: text.length < full.length }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function collectPrStatus(cwd: string): Promise<PrStatusResult> {
|
|
209
|
+
const gh = Bun.which('gh')
|
|
210
|
+
if (gh === null) return { kind: 'no-gh' }
|
|
211
|
+
|
|
212
|
+
const result = await runCli(gh, ['pr', 'view', '--json', PR_VIEW_FIELDS], 15_000, cwd)
|
|
213
|
+
if (!result.ok) {
|
|
214
|
+
// `gh` exits non-zero for every "there is simply nothing to show" case too:
|
|
215
|
+
// no PR on the branch, no GitHub remote, or a directory that isn't a repo
|
|
216
|
+
// at all (aimux projects can point anywhere). None of those is an error.
|
|
217
|
+
const stderr = result.stderr.toLowerCase()
|
|
218
|
+
if (NOT_AN_ERROR.some((needle) => stderr.includes(needle))) return { kind: 'no-pr' }
|
|
219
|
+
return { kind: 'error', message: (result.error ?? 'gh failed').slice(0, 200) }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
return parsePrView(JSON.parse(result.stdout))
|
|
224
|
+
} catch {
|
|
225
|
+
return { kind: 'no-pr' }
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -79,7 +79,7 @@ const cache = new Map<string, CacheEntry>()
|
|
|
79
79
|
/**
|
|
80
80
|
* Cached variant. The cache key is `projectPath` + `maxDepth`: a depth change
|
|
81
81
|
* invalidates the entry. The cache persists until `invalidateRepoCache()` or
|
|
82
|
-
* process exit — discovery is one-shot per
|
|
82
|
+
* process exit — discovery is one-shot per project per depth setting.
|
|
83
83
|
*/
|
|
84
84
|
export async function discoverRepos(projectPath: string, maxDepth = 1): Promise<DiscoveredRepo[]> {
|
|
85
85
|
const existing = cache.get(projectPath)
|
|
@@ -5,7 +5,7 @@ import { dispatchGlobal } from '../state/dispatch-ref'
|
|
|
5
5
|
import { discoverRepos } from './repo-discovery'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Discover nested git repos inside `projectPath` once per
|
|
8
|
+
* Discover nested git repos inside `projectPath` once per project and push
|
|
9
9
|
* the result into state. No-op when the multi-repo config flag is off.
|
|
10
10
|
*/
|
|
11
11
|
export function useRepoDiscovery(projectPath: string | undefined): void {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import { appStore } from '../state/app-store'
|
|
4
|
+
import { dispatchGlobal } from '../state/dispatch-ref'
|
|
5
|
+
import { getCurrentBranch } from '../ui/git-branch'
|
|
6
|
+
|
|
7
|
+
const INTERVAL_MS = 4000
|
|
8
|
+
|
|
9
|
+
// Single source of truth for "what git branch is each workspace on right now".
|
|
10
|
+
// Polls every known workspace's path and dispatches update-workspace-record
|
|
11
|
+
// when the live branch differs from the stored one. Components read
|
|
12
|
+
// `workspace.branch` from state — no per-component polling, no flickers.
|
|
13
|
+
//
|
|
14
|
+
// Runs once when enabled; reads projects from the store on each tick so
|
|
15
|
+
// project updates do NOT re-create the effect (which would otherwise feedback
|
|
16
|
+
// off our own dispatches).
|
|
17
|
+
export function useWorkspaceBranchPolling(enabled: boolean): void {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!enabled) return
|
|
20
|
+
|
|
21
|
+
let cancelled = false
|
|
22
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
23
|
+
|
|
24
|
+
const tick = async () => {
|
|
25
|
+
const projects = appStore.getState().projects
|
|
26
|
+
await Promise.all(
|
|
27
|
+
projects.flatMap((project) =>
|
|
28
|
+
(project.workspaces ?? []).map(async (workspace) => {
|
|
29
|
+
if (workspace.path == null || workspace.path === '') return
|
|
30
|
+
const branch = await getCurrentBranch(workspace.path)
|
|
31
|
+
if (cancelled) return
|
|
32
|
+
if (branch != null && branch !== workspace.branch) {
|
|
33
|
+
dispatchGlobal({
|
|
34
|
+
patch: { branch },
|
|
35
|
+
projectId: project.id,
|
|
36
|
+
type: 'update-workspace-record',
|
|
37
|
+
workspaceId: workspace.id,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
if (cancelled) return
|
|
44
|
+
timer = setTimeout(() => void tick(), INTERVAL_MS)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
void tick()
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
cancelled = true
|
|
51
|
+
if (timer != null) clearTimeout(timer)
|
|
52
|
+
}
|
|
53
|
+
}, [enabled])
|
|
54
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { BranchDivergence } from '../state/types'
|
|
4
|
+
|
|
5
|
+
import { useAppStore } from '../state/app-store'
|
|
6
|
+
import { dispatchGlobal } from '../state/dispatch-ref'
|
|
7
|
+
import { getBranchDivergence, getWorkspaceDiffStat } from './divergence'
|
|
8
|
+
|
|
9
|
+
const INTERVAL_MS = 4000
|
|
10
|
+
|
|
11
|
+
// Polls per-workspace base divergence for the current project while enabled and
|
|
12
|
+
// dispatches it into workspaceDivergence. Only workspaces that record a baseRef
|
|
13
|
+
// (the aimux-created ones) are measured; the primary and externally-discovered
|
|
14
|
+
// workspaces have no recorded base and are left out.
|
|
15
|
+
export function useWorkspaceDivergencePolling(enabled: boolean): void {
|
|
16
|
+
const currentProjectId = useAppStore((s) => s.currentProjectId)
|
|
17
|
+
const projects = useAppStore((s) => s.projects)
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!enabled) return
|
|
21
|
+
const project =
|
|
22
|
+
currentProjectId != null && currentProjectId !== ''
|
|
23
|
+
? projects.find((s) => s.id === currentProjectId)
|
|
24
|
+
: undefined
|
|
25
|
+
const targets = (project?.workspaces ?? []).filter(
|
|
26
|
+
(w) => w.baseRef != null && w.baseRef !== '' && w.branch != null && w.branch !== ''
|
|
27
|
+
)
|
|
28
|
+
if (targets.length === 0) return
|
|
29
|
+
|
|
30
|
+
let cancelled = false
|
|
31
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
32
|
+
|
|
33
|
+
const tick = async () => {
|
|
34
|
+
const entries = await Promise.all(
|
|
35
|
+
targets.map(async (workspace) => {
|
|
36
|
+
const base = workspace.baseRef
|
|
37
|
+
const branch = workspace.branch
|
|
38
|
+
if (base == null || branch == null) return null
|
|
39
|
+
// Commits come from the repo root (comparing two refs); lines come
|
|
40
|
+
// from the workspace itself, so uncommitted work is counted too.
|
|
41
|
+
const [divergence, stat] = await Promise.all([
|
|
42
|
+
getBranchDivergence(workspace.repoRoot, base, branch),
|
|
43
|
+
getWorkspaceDiffStat(workspace.path, base),
|
|
44
|
+
])
|
|
45
|
+
if (divergence == null) return null
|
|
46
|
+
return [workspace.id, { ...divergence, ...stat }] as const
|
|
47
|
+
})
|
|
48
|
+
)
|
|
49
|
+
if (cancelled) return
|
|
50
|
+
const next: Record<string, BranchDivergence> = {}
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry != null) next[entry[0]] = entry[1]
|
|
53
|
+
}
|
|
54
|
+
dispatchGlobal({ divergence: next, type: 'set-workspace-divergence' })
|
|
55
|
+
timer = setTimeout(() => void tick(), INTERVAL_MS)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
void tick()
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
cancelled = true
|
|
62
|
+
if (timer != null) clearTimeout(timer)
|
|
63
|
+
}
|
|
64
|
+
}, [enabled, currentProjectId, projects])
|
|
65
|
+
}
|
package/src/git/worktree.ts
CHANGED
|
@@ -69,6 +69,35 @@ export async function resolveGitRef(repoPath: string, ref: string): Promise<stri
|
|
|
69
69
|
return result.text().trim() || undefined
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// The branch a new workspace forks from unless the user picks another base.
|
|
73
|
+
// `origin/HEAD` is the only authoritative answer; it is missing on repos cloned
|
|
74
|
+
// with --no-checkout or created locally, hence the name probes behind it.
|
|
75
|
+
export async function getDefaultBranch(repoPath: string): Promise<string | undefined> {
|
|
76
|
+
const head = await $`git -C ${repoPath} symbolic-ref --short refs/remotes/origin/HEAD`
|
|
77
|
+
.quiet()
|
|
78
|
+
.nothrow()
|
|
79
|
+
if (head.exitCode === 0) {
|
|
80
|
+
const ref = head.text().trim()
|
|
81
|
+
const short = ref.startsWith('origin/') ? ref.slice('origin/'.length) : ref
|
|
82
|
+
if (short !== '') return short
|
|
83
|
+
}
|
|
84
|
+
const locals = await listLocalBranches(repoPath)
|
|
85
|
+
return ['main', 'master'].find((name) => locals.includes(name))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Rename a local branch. Returns false (without throwing) when git refuses —
|
|
89
|
+
// the target name already exists, most likely — so a failed rename leaves the
|
|
90
|
+
// workspace on the branch it was created with instead of losing it.
|
|
91
|
+
export async function renameGitBranch(
|
|
92
|
+
repoPath: string,
|
|
93
|
+
from: string,
|
|
94
|
+
to: string
|
|
95
|
+
): Promise<boolean> {
|
|
96
|
+
if (from === '' || to === '' || from === to) return false
|
|
97
|
+
const result = await $`git -C ${repoPath} branch -m ${from} ${to}`.quiet().nothrow()
|
|
98
|
+
return result.exitCode === 0
|
|
99
|
+
}
|
|
100
|
+
|
|
72
101
|
export async function createGitWorktree({
|
|
73
102
|
baseRef,
|
|
74
103
|
branchName,
|
package/src/index.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
const command = process.argv[2]
|
|
6
6
|
|
|
7
7
|
// Shell completion. First branch and cheapest: it loads the CLI registry and
|
|
8
|
-
// nothing else — no daemon client, no
|
|
8
|
+
// nothing else — no daemon client, no project backend, no renderer.
|
|
9
9
|
if (command === '__complete') {
|
|
10
10
|
const { runComplete } = await import('./cli/completion/entry')
|
|
11
11
|
process.exit(await runComplete(process.argv.slice(3)))
|
|
@@ -19,7 +19,7 @@ if (command === 'completion') {
|
|
|
19
19
|
// CLI control plane (docs/reference/cli.md). Branch BEFORE the UI bootstrap so
|
|
20
20
|
// `aimux tab list` from a non-TTY shell never spins up the React renderer.
|
|
21
21
|
// Dynamic import keeps the CLI code out of the UI's cold-start cost.
|
|
22
|
-
const CLI_GROUPS = new Set(['tab', '
|
|
22
|
+
const CLI_GROUPS = new Set(['tab', 'project', 'workspace', 'worker'])
|
|
23
23
|
if (typeof command === 'string' && CLI_GROUPS.has(command)) {
|
|
24
24
|
const { runCli } = await import('./cli')
|
|
25
25
|
process.exit(await runCli(process.argv.slice(2)))
|
|
@@ -72,8 +72,14 @@ if (command === '--help' || command === '-h' || command === 'help') {
|
|
|
72
72
|
process.exit(await runCli([]))
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Sequential ON PURPOSE: @opentui/react evaluates @opentui/core as part of its
|
|
76
|
+
// own module graph. Loading both concurrently races the two evaluations and
|
|
77
|
+
// react's chunk can hit `class X extends TextNodeRenderable` while core is
|
|
78
|
+
// still initializing (ReferenceError: cannot access before initialization).
|
|
79
|
+
// react has to wait on core either way, so this costs nothing.
|
|
80
|
+
const { createCliRenderer } = await import('@opentui/core')
|
|
81
|
+
|
|
75
82
|
const [
|
|
76
|
-
{ createCliRenderer },
|
|
77
83
|
{ createRoot },
|
|
78
84
|
{ App },
|
|
79
85
|
{ loadUserConfig },
|
|
@@ -82,7 +88,6 @@ const [
|
|
|
82
88
|
{ createSessionBackend },
|
|
83
89
|
{ maybeAutoInstallCompletion },
|
|
84
90
|
] = await Promise.all([
|
|
85
|
-
import('@opentui/core'),
|
|
86
91
|
import('@opentui/react'),
|
|
87
92
|
import('./app'),
|
|
88
93
|
import('./config/loader'),
|
|
@@ -91,7 +96,7 @@ const [
|
|
|
91
96
|
import('./session-backend/bootstrap'),
|
|
92
97
|
import('./cli/completion/install'),
|
|
93
98
|
])
|
|
94
|
-
const resolvedConfig = await loadUserConfig()
|
|
99
|
+
const { resolved: resolvedConfig, user: userConfig } = await loadUserConfig()
|
|
95
100
|
|
|
96
101
|
// First launch (and after every upgrade): drop the shell completion script in
|
|
97
102
|
// the conventional location for $SHELL. Silent and best-effort — it writes one
|
|
@@ -149,4 +154,4 @@ const backend = await createSessionBackend({
|
|
|
149
154
|
})
|
|
150
155
|
logDebug('index.backendReady', { backend: backend.constructor.name, runtimeProfile })
|
|
151
156
|
|
|
152
|
-
root.render(<App backend={backend} resolvedConfig={resolvedConfig} />)
|
|
157
|
+
root.render(<App backend={backend} resolvedConfig={resolvedConfig} userConfig={userConfig} />)
|
|
@@ -11,15 +11,17 @@ export const HELP_MODE_LABELS: { modeId: ModeId; label: string }[] = [
|
|
|
11
11
|
{ label: 'Navigation', modeId: 'navigation' },
|
|
12
12
|
{ label: 'Terminal input', modeId: 'terminal-input' },
|
|
13
13
|
{ label: 'Git mode', modeId: 'git-mode' },
|
|
14
|
+
{ label: 'Settings', modeId: 'settings' },
|
|
14
15
|
{ label: 'Git commit', modeId: 'modal.git-commit' },
|
|
15
16
|
{ label: 'New tab', modeId: 'modal.new-tab.command-edit' },
|
|
16
17
|
{ label: 'New tab — command', modeId: 'modal.new-tab.command-edit' },
|
|
17
|
-
{ label: '
|
|
18
|
-
{ label: '
|
|
19
|
-
{ label: '
|
|
20
|
-
{ label: 'Create
|
|
18
|
+
{ label: 'Project picker', modeId: 'modal.project-picker.filtering' },
|
|
19
|
+
{ label: 'Project picker — filter', modeId: 'modal.project-picker.filtering' },
|
|
20
|
+
{ label: 'Project name', modeId: 'modal.project-name' },
|
|
21
|
+
{ label: 'Create project', modeId: 'modal.create-project' },
|
|
22
|
+
{ label: 'Create workspace', modeId: 'modal.create-workspace' },
|
|
21
23
|
{ label: 'Rename tab', modeId: 'modal.rename-tab' },
|
|
22
|
-
{ label: 'Rename
|
|
24
|
+
{ label: 'Rename workspace', modeId: 'modal.rename-workspace' },
|
|
23
25
|
{ label: 'Snippet picker', modeId: 'modal.snippet-picker.filtering' },
|
|
24
26
|
{ label: 'Snippet picker — filter', modeId: 'modal.snippet-picker.filtering' },
|
|
25
27
|
{ label: 'Snippet editor', modeId: 'modal.snippet-editor' },
|
|
@@ -6,18 +6,22 @@ type SupportedModalType = Exclude<ModalType, null>
|
|
|
6
6
|
const DIRECT_FOCUS_MODE_IDS: Partial<Record<FocusMode, ModeId>> = {
|
|
7
7
|
'git': 'git-mode',
|
|
8
8
|
'navigation': 'navigation',
|
|
9
|
+
'settings': 'settings',
|
|
9
10
|
'terminal-input': 'terminal-input',
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const COMMAND_EDIT_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
|
|
13
|
-
'create-
|
|
14
|
+
'create-project': 'modal.create-project',
|
|
15
|
+
'create-workspace': 'modal.create-workspace',
|
|
14
16
|
'git-commit': 'modal.git-commit',
|
|
15
17
|
'help': 'modal.help.filtering',
|
|
16
18
|
'new-tab': 'modal.new-tab.command-edit',
|
|
19
|
+
'project-name': 'modal.project-name',
|
|
20
|
+
'project-picker': 'modal.project-picker.filtering',
|
|
17
21
|
'rename-tab': 'modal.rename-tab',
|
|
18
|
-
'rename-
|
|
19
|
-
'
|
|
20
|
-
'
|
|
22
|
+
'rename-workspace': 'modal.rename-workspace',
|
|
23
|
+
'setting-text': 'modal.setting-text',
|
|
24
|
+
'settings-search': 'modal.settings-search.filtering',
|
|
21
25
|
'snippet-editor': 'modal.snippet-editor',
|
|
22
26
|
'snippet-picker': 'modal.snippet-picker.filtering',
|
|
23
27
|
'split-picker': 'modal.split-picker',
|
|
@@ -27,9 +31,9 @@ const COMMAND_EDIT_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
|
|
|
27
31
|
const MODAL_MODE_IDS: Partial<Record<SupportedModalType, ModeId>> = {
|
|
28
32
|
'ai-usage': 'modal.ai-usage',
|
|
29
33
|
'update-available': 'modal.update-available',
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
34
|
+
'workspace-delete-confirm': 'modal.workspace-delete-confirm',
|
|
35
|
+
'workspace-move': 'modal.workspace-move',
|
|
36
|
+
'workspace-move-confirm': 'modal.workspace-move-confirm',
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export function deriveModeId(state: AppState): ModeId {
|
|
@@ -41,8 +45,8 @@ export function deriveModeId(state: AppState): ModeId {
|
|
|
41
45
|
|
|
42
46
|
// Overlay on top of git mode without flipping focusMode (like help) — route
|
|
43
47
|
// input to the picker while it's open even though focus stays 'git'.
|
|
44
|
-
if (state.modal.type === '
|
|
45
|
-
return 'modal.
|
|
48
|
+
if (state.modal.type === 'workspace-move') {
|
|
49
|
+
return 'modal.workspace-move'
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
// Flash-jump overlay: same pattern — renders on top of navigation, all
|
|
@@ -60,9 +64,6 @@ export function deriveModeId(state: AppState): ModeId {
|
|
|
60
64
|
if (state.modal.type === 'new-tab' && state.modal.editingCommand !== null) {
|
|
61
65
|
return 'modal.new-tab.editing-command'
|
|
62
66
|
}
|
|
63
|
-
if (state.modal.type === 'new-tab' && state.modal.worktreeDeletePrompt !== null) {
|
|
64
|
-
return 'modal.new-tab.worktree-delete-confirm'
|
|
65
|
-
}
|
|
66
67
|
if (state.modal.type === 'git-commit' && state.modal.stage === 'confirm') {
|
|
67
68
|
return 'modal.git-commit.confirm'
|
|
68
69
|
}
|
|
@@ -1,51 +1,64 @@
|
|
|
1
1
|
import type { ModeId } from './types'
|
|
2
2
|
|
|
3
3
|
const TRANSITIONS: Record<ModeId, readonly ModeId[]> = {
|
|
4
|
-
'git-mode': ['navigation', 'modal.git-commit', 'modal.
|
|
4
|
+
'git-mode': ['navigation', 'modal.git-commit', 'modal.workspace-move'],
|
|
5
5
|
'modal.ai-usage': ['navigation', 'terminal-input'],
|
|
6
|
-
'modal.create-
|
|
6
|
+
'modal.create-project': ['navigation', 'modal.project-picker.filtering'],
|
|
7
|
+
'modal.create-workspace': ['navigation', 'terminal-input'],
|
|
7
8
|
'modal.flash-jump': ['navigation'],
|
|
8
9
|
'modal.git-commit': ['git-mode', 'modal.git-commit.confirm', 'modal.git-commit.generating'],
|
|
9
10
|
'modal.git-commit.confirm': ['modal.git-commit', 'git-mode'],
|
|
10
11
|
'modal.git-commit.generating': ['modal.git-commit', 'modal.git-commit.confirm', 'git-mode'],
|
|
11
12
|
'modal.help.filtering': ['navigation'],
|
|
12
|
-
'modal.new-tab.command-edit': [
|
|
13
|
-
'navigation',
|
|
14
|
-
'modal.new-tab.editing-command',
|
|
15
|
-
'modal.new-tab.worktree-delete-confirm',
|
|
16
|
-
],
|
|
13
|
+
'modal.new-tab.command-edit': ['navigation', 'modal.new-tab.editing-command'],
|
|
17
14
|
'modal.new-tab.editing-command': ['navigation', 'modal.new-tab.command-edit'],
|
|
18
|
-
'modal.
|
|
15
|
+
'modal.project-name': ['modal.project-picker.filtering', 'navigation'],
|
|
16
|
+
'modal.project-picker.filtering': ['navigation', 'modal.project-name', 'modal.create-project'],
|
|
19
17
|
'modal.rename-tab': ['navigation'],
|
|
20
|
-
'modal.rename-
|
|
21
|
-
'modal.
|
|
22
|
-
'modal.
|
|
18
|
+
'modal.rename-workspace': ['navigation'],
|
|
19
|
+
'modal.setting-text': ['settings'],
|
|
20
|
+
'modal.settings-search.filtering': ['settings'],
|
|
23
21
|
'modal.snippet-editor': ['navigation', 'modal.snippet-picker.filtering'],
|
|
24
|
-
|
|
22
|
+
// Both pickers are reachable from the settings screen as well as from the
|
|
23
|
+
// panes, and `returnTo` sends each one back where it came from.
|
|
24
|
+
'modal.snippet-picker.filtering': ['navigation', 'settings', 'modal.snippet-editor'],
|
|
25
25
|
'modal.split-picker': ['navigation', 'terminal-input'],
|
|
26
|
-
'modal.theme-picker.filtering': ['navigation'],
|
|
26
|
+
'modal.theme-picker.filtering': ['navigation', 'settings'],
|
|
27
27
|
'modal.update-available': ['navigation'],
|
|
28
|
-
'modal.
|
|
29
|
-
'modal.
|
|
30
|
-
'modal.
|
|
28
|
+
'modal.workspace-delete-confirm': ['navigation'],
|
|
29
|
+
'modal.workspace-move': ['git-mode', 'navigation'],
|
|
30
|
+
'modal.workspace-move-confirm': ['navigation'],
|
|
31
31
|
'navigation': [
|
|
32
32
|
'terminal-input',
|
|
33
|
+
'modal.create-workspace',
|
|
33
34
|
'modal.new-tab.command-edit',
|
|
34
35
|
'modal.new-tab.editing-command',
|
|
35
|
-
'modal.
|
|
36
|
+
'modal.project-picker.filtering',
|
|
36
37
|
'modal.help.filtering',
|
|
37
38
|
'modal.snippet-picker.filtering',
|
|
38
39
|
'modal.theme-picker.filtering',
|
|
39
40
|
'modal.rename-tab',
|
|
40
|
-
'modal.rename-
|
|
41
|
+
'modal.rename-workspace',
|
|
41
42
|
'modal.update-available',
|
|
42
43
|
'modal.ai-usage',
|
|
43
|
-
'modal.
|
|
44
|
-
'modal.
|
|
44
|
+
'modal.workspace-delete-confirm',
|
|
45
|
+
'modal.workspace-move-confirm',
|
|
45
46
|
'modal.flash-jump',
|
|
46
47
|
'git-mode',
|
|
48
|
+
'settings',
|
|
49
|
+
],
|
|
50
|
+
// The help overlay opens over settings without a transition (it leaves
|
|
51
|
+
// focusMode alone). The two pickers an action row hands over to are dispatched
|
|
52
|
+
// directly rather than through a KeyResult, but they are listed because that is
|
|
53
|
+
// what this table is for: saying where a mode can go.
|
|
54
|
+
'settings': [
|
|
55
|
+
'navigation',
|
|
56
|
+
'modal.setting-text',
|
|
57
|
+
'modal.settings-search.filtering',
|
|
58
|
+
'modal.theme-picker.filtering',
|
|
59
|
+
'modal.snippet-picker.filtering',
|
|
47
60
|
],
|
|
48
|
-
'terminal-input': ['navigation', 'modal.split-picker', 'modal.ai-usage'],
|
|
61
|
+
'terminal-input': ['navigation', 'modal.split-picker', 'modal.ai-usage', 'settings'],
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
export function isValidTransition(from: ModeId, to: ModeId): boolean {
|