@brimveyn/aimux 1.21.0 → 1.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +302 -1541
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +2 -1
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +18 -14
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +93 -48
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +70 -112
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-status.ts +1 -1
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +4 -4
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +19 -6
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +1 -1
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/layout-resize.ts +2 -2
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -6
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +4 -3
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +244 -405
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +46 -36
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +139 -121
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +6 -3
- package/src/ui/widgets/widget-context-menu.ts +26 -10
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -43
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AppAction
|
|
1
|
+
import type { AppAction } from '../actions'
|
|
2
|
+
import type { AppState, TabSession } from '../types'
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
allLeafIds,
|
|
@@ -14,12 +15,13 @@ import {
|
|
|
14
15
|
setSplitRatio,
|
|
15
16
|
splitNode,
|
|
16
17
|
} from '../layout-tree'
|
|
17
|
-
import { normalizeGroupedTabOrder } from '../
|
|
18
|
+
import { normalizeGroupedTabOrder } from '../project-persistence'
|
|
18
19
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
clearWorkspaceDone,
|
|
21
|
+
filterTabsForActiveWorkspace,
|
|
22
|
+
orderTabsByWorkspace,
|
|
23
|
+
withActiveWorkspace,
|
|
24
|
+
} from '../project-workspaces'
|
|
23
25
|
import { createDefaultTerminalModes } from '../terminal-modes'
|
|
24
26
|
|
|
25
27
|
const MAX_BUFFER_LENGTH = 50_000
|
|
@@ -197,47 +199,61 @@ function getActiveIndex(state: AppState): number {
|
|
|
197
199
|
return state.tabs.findIndex((tab) => tab.id === state.activeTabId)
|
|
198
200
|
}
|
|
199
201
|
|
|
200
|
-
function
|
|
201
|
-
return state.
|
|
202
|
-
? state.
|
|
202
|
+
function getCurrentProject(state: AppState) {
|
|
203
|
+
return state.currentProjectId != null && state.currentProjectId !== ''
|
|
204
|
+
? state.projects.find((project) => project.id === state.currentProjectId)
|
|
203
205
|
: undefined
|
|
204
206
|
}
|
|
205
207
|
|
|
206
|
-
function
|
|
208
|
+
function withActiveTabWorkspace(
|
|
207
209
|
state: AppState,
|
|
208
210
|
tabId: string | null,
|
|
209
211
|
opts?: { onlyIfMissing?: boolean }
|
|
210
212
|
): AppState {
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
const tab =
|
|
218
|
-
if (!(tab?.
|
|
219
|
-
const
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
// a
|
|
223
|
-
|
|
213
|
+
if (tabId == null || tabId === '') return state
|
|
214
|
+
// Opening the tab is reading its notification, whatever else this helper
|
|
215
|
+
// decides about the workspace. Every path that makes a tab the active one
|
|
216
|
+
// goes through here.
|
|
217
|
+
const seen = { ...state, tabs: clearTabUnseen(state.tabs, tabId) }
|
|
218
|
+
if (!(seen.currentProjectId != null && seen.currentProjectId !== '')) return seen
|
|
219
|
+
const tab = seen.tabs.find((entry) => entry.id === tabId)
|
|
220
|
+
if (!(tab?.workspaceId != null && tab?.workspaceId !== '')) return seen
|
|
221
|
+
const workspaceId = tab.workspaceId
|
|
222
|
+
// Opening one of a workspace's tabs is seeing that workspace, so it drops the
|
|
223
|
+
// finished-a-turn tick — otherwise a background tab finishing in the workspace
|
|
224
|
+
// you are already in leaves a ✓ that only a workspace switch could clear.
|
|
225
|
+
const workspaceActivity = clearWorkspaceDone(seen.workspaceActivity, workspaceId)
|
|
226
|
+
// When `onlyIfMissing` is set, we leave the project's workspace alone if it
|
|
227
|
+
// already points at a known workspace. Used by `hydrate-project` so that
|
|
228
|
+
// a backend re-attach after a user-initiated workspace switch (j/k cycling)
|
|
229
|
+
// doesn't clobber the freshly chosen workspace by re-syncing from the
|
|
224
230
|
// restored active tab.
|
|
225
231
|
if (opts?.onlyIfMissing === true) {
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
(
|
|
231
|
-
if (
|
|
232
|
+
const project = seen.projects.find((entry) => entry.id === seen.currentProjectId)
|
|
233
|
+
const hasValidWorkspace =
|
|
234
|
+
project?.activeWorkspaceId != null &&
|
|
235
|
+
project.activeWorkspaceId !== '' &&
|
|
236
|
+
(project.workspaces?.some((w) => w.id === project.activeWorkspaceId) ?? false)
|
|
237
|
+
if (hasValidWorkspace) return { ...seen, workspaceActivity }
|
|
232
238
|
}
|
|
233
239
|
return {
|
|
234
|
-
...
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
...seen,
|
|
241
|
+
projects: seen.projects.map((project) =>
|
|
242
|
+
project.id === seen.currentProjectId ? withActiveWorkspace(project, workspaceId) : project
|
|
237
243
|
),
|
|
244
|
+
workspaceActivity,
|
|
238
245
|
}
|
|
239
246
|
}
|
|
240
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Drop one tab's unseen mark. Returns the same array when it holds none, so a
|
|
250
|
+
* caller can run it on every activation without re-rendering the tab list.
|
|
251
|
+
*/
|
|
252
|
+
function clearTabUnseen(tabs: TabSession[], tabId: string): TabSession[] {
|
|
253
|
+
if (!tabs.some((tab) => tab.id === tabId && tab.unseen === true)) return tabs
|
|
254
|
+
return tabs.map((tab) => (tab.id === tabId ? { ...tab, unseen: undefined } : tab))
|
|
255
|
+
}
|
|
256
|
+
|
|
241
257
|
function closeTabAtIndex(state: AppState, indexToClose: number): AppState {
|
|
242
258
|
if (indexToClose < 0 || indexToClose >= state.tabs.length) {
|
|
243
259
|
return state
|
|
@@ -262,7 +278,12 @@ function closeTabAtIndex(state: AppState, indexToClose: number): AppState {
|
|
|
262
278
|
getAdjacentLeaf(groupTree, closingTabId, 'down') ??
|
|
263
279
|
getAdjacentLeaf(groupTree, closingTabId, 'up'))
|
|
264
280
|
: null
|
|
265
|
-
|
|
281
|
+
// Positional fallbacks must skip hidden tabs: landing `activeTabId` on one
|
|
282
|
+
// would render a pane the user cannot see in any tab bar.
|
|
283
|
+
const positional = [tabs[indexToClose], tabs[indexToClose - 1]].find(
|
|
284
|
+
(tab) => tab !== undefined && tab.hidden !== true
|
|
285
|
+
)
|
|
286
|
+
nextActiveTabId = layoutNeighbor ?? positional?.id ?? null
|
|
266
287
|
} else {
|
|
267
288
|
nextActiveTabId = tabs.find((tab) => tab.id === state.activeTabId)?.id ?? null
|
|
268
289
|
}
|
|
@@ -292,7 +313,7 @@ function closeTabAtIndex(state: AppState, indexToClose: number): AppState {
|
|
|
292
313
|
}
|
|
293
314
|
}
|
|
294
315
|
|
|
295
|
-
return
|
|
316
|
+
return withActiveTabWorkspace(
|
|
296
317
|
{
|
|
297
318
|
...state,
|
|
298
319
|
activeTabId: nextActiveTabId,
|
|
@@ -309,35 +330,41 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
309
330
|
switch (action.type) {
|
|
310
331
|
case 'add-tab': {
|
|
311
332
|
const newTab = { ...action.tab, activity: action.tab.activity ?? 'idle' }
|
|
312
|
-
|
|
333
|
+
// A hidden tab is chrome-invisible, so adding it must be invisible too:
|
|
334
|
+
// no focus steal, no modal dismissal, and — critically — no workspace
|
|
335
|
+
// re-sync, which would teleport a user sitting on another workspace.
|
|
336
|
+
if (newTab.hidden === true) {
|
|
337
|
+
return { ...state, tabs: [...state.tabs, newTab] }
|
|
338
|
+
}
|
|
339
|
+
return withActiveTabWorkspace(
|
|
313
340
|
{
|
|
314
341
|
...state,
|
|
315
342
|
activeTabId: newTab.id,
|
|
316
343
|
focusMode: 'navigation',
|
|
317
|
-
modal: { editBuffer: null,
|
|
344
|
+
modal: { editBuffer: null, projectTargetId: null, selectedIndex: 0, type: null },
|
|
318
345
|
tabs: [...state.tabs, newTab],
|
|
319
346
|
},
|
|
320
347
|
newTab.id
|
|
321
348
|
)
|
|
322
349
|
}
|
|
323
|
-
case 'hydrate-
|
|
324
|
-
// The
|
|
350
|
+
case 'hydrate-project': {
|
|
351
|
+
// The project's activeWorkspaceId may have just been switched by the
|
|
325
352
|
// user (j/k cycle, sidebar click) before the backend attached. Honor
|
|
326
|
-
// that choice by only considering tabs visible in that
|
|
353
|
+
// that choice by only considering tabs visible in that workspace —
|
|
327
354
|
// otherwise we'd land the active tab on whatever the backend picked
|
|
328
|
-
// (typically the
|
|
329
|
-
const
|
|
330
|
-
state.
|
|
331
|
-
? state.
|
|
355
|
+
// (typically the workspace we *last* persisted, not the current one).
|
|
356
|
+
const currentProject =
|
|
357
|
+
state.currentProjectId != null && state.currentProjectId !== ''
|
|
358
|
+
? state.projects.find((s) => s.id === state.currentProjectId)
|
|
332
359
|
: undefined
|
|
333
|
-
const
|
|
334
|
-
const visibleIds = new Set(
|
|
360
|
+
const visibleForWorkspace = filterTabsForActiveWorkspace(action.tabs, currentProject)
|
|
361
|
+
const visibleIds = new Set(visibleForWorkspace.map((t) => t.id))
|
|
335
362
|
const hydratedActiveTabId =
|
|
336
363
|
action.activeTabId != null &&
|
|
337
364
|
action.activeTabId !== '' &&
|
|
338
365
|
visibleIds.has(action.activeTabId)
|
|
339
366
|
? action.activeTabId
|
|
340
|
-
: (
|
|
367
|
+
: (visibleForWorkspace[0]?.id ?? null)
|
|
341
368
|
const tabIds = new Set(action.tabs.map((t) => t.id))
|
|
342
369
|
|
|
343
370
|
// Restore from new multi-tree format or migrate from legacy single tree
|
|
@@ -368,7 +395,7 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
368
395
|
}
|
|
369
396
|
}
|
|
370
397
|
|
|
371
|
-
return
|
|
398
|
+
return withActiveTabWorkspace(
|
|
372
399
|
{
|
|
373
400
|
...state,
|
|
374
401
|
activeTabId: hydratedActiveTabId,
|
|
@@ -389,24 +416,24 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
389
416
|
case 'close-active-tab':
|
|
390
417
|
return closeTabAtIndex(state, getActiveIndex(state))
|
|
391
418
|
case 'set-active-tab':
|
|
392
|
-
return
|
|
419
|
+
return withActiveTabWorkspace({ ...state, activeTabId: action.tabId }, action.tabId)
|
|
393
420
|
case 'move-active-tab': {
|
|
394
421
|
if (state.tabs.length === 0) {
|
|
395
422
|
return state
|
|
396
423
|
}
|
|
397
|
-
const
|
|
398
|
-
const visibleTabs =
|
|
424
|
+
const project = getCurrentProject(state)
|
|
425
|
+
const visibleTabs = filterTabsForActiveWorkspace(state.tabs, project)
|
|
399
426
|
if (visibleTabs.length === 0) {
|
|
400
427
|
return state
|
|
401
428
|
}
|
|
402
|
-
const orderedTabs =
|
|
429
|
+
const orderedTabs = orderTabsByWorkspace(visibleTabs, project)
|
|
403
430
|
const currentIndex = orderedTabs.findIndex((tab) => tab.id === state.activeTabId)
|
|
404
431
|
const safeIndex = currentIndex === -1 ? 0 : currentIndex
|
|
405
432
|
const nextIndex = (safeIndex + action.delta + orderedTabs.length) % orderedTabs.length
|
|
406
433
|
const nextTabId = orderedTabs[nextIndex]?.id
|
|
407
434
|
return nextTabId == null || nextTabId === '' || nextTabId === state.activeTabId
|
|
408
435
|
? state
|
|
409
|
-
:
|
|
436
|
+
: withActiveTabWorkspace({ ...state, activeTabId: nextTabId }, nextTabId)
|
|
410
437
|
}
|
|
411
438
|
case 'reorder-active-tab': {
|
|
412
439
|
const activeIndex = getActiveIndex(state)
|
|
@@ -436,8 +463,14 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
436
463
|
return { ...state, tabs }
|
|
437
464
|
}
|
|
438
465
|
|
|
439
|
-
// Standalone tab reorder
|
|
440
|
-
|
|
466
|
+
// Standalone tab reorder. Step over hidden tabs rather than swapping with
|
|
467
|
+
// one: the swap would look like a dead keypress and shuffle the hidden
|
|
468
|
+
// tab out of its append-order slot.
|
|
469
|
+
let nextIndex = activeIndex + action.delta
|
|
470
|
+
const step = action.delta < 0 ? -1 : 1
|
|
471
|
+
while (state.tabs[nextIndex]?.hidden === true) {
|
|
472
|
+
nextIndex += step
|
|
473
|
+
}
|
|
441
474
|
if (nextIndex < 0 || nextIndex >= state.tabs.length) {
|
|
442
475
|
return state
|
|
443
476
|
}
|
|
@@ -473,7 +506,7 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
473
506
|
// Drag-and-drop reorder of the visible tab strip. `orderedTabIds` is the
|
|
474
507
|
// flattened new order of the currently-visible tabs (group members already
|
|
475
508
|
// expanded into contiguous runs). We only rewrite the slots those tabs
|
|
476
|
-
// occupy in `state.tabs`, leaving tabs from other
|
|
509
|
+
// occupy in `state.tabs`, leaving tabs from other workspaces anchored in
|
|
477
510
|
// place. Because group members arrive contiguous, splits stay intact.
|
|
478
511
|
const visibleSet = new Set(action.orderedTabIds)
|
|
479
512
|
const byId = new Map(state.tabs.map((tab) => [tab.id, tab]))
|
|
@@ -486,8 +519,8 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
486
519
|
})
|
|
487
520
|
return { ...state, tabs }
|
|
488
521
|
}
|
|
489
|
-
case 'reset-tab-
|
|
490
|
-
return
|
|
522
|
+
case 'reset-tab-project':
|
|
523
|
+
return withActiveTabWorkspace(
|
|
491
524
|
{
|
|
492
525
|
...state,
|
|
493
526
|
activeTabId: action.tabId,
|
|
@@ -527,7 +560,20 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
527
560
|
case 'set-tab-activity':
|
|
528
561
|
return {
|
|
529
562
|
...state,
|
|
530
|
-
tabs: updateTab(state.tabs, action.tabId, (tab) => ({
|
|
563
|
+
tabs: updateTab(state.tabs, action.tabId, (tab) => ({
|
|
564
|
+
...tab,
|
|
565
|
+
activity: action.activity,
|
|
566
|
+
// Back to work: whatever it wanted to tell you is out of date.
|
|
567
|
+
unseen:
|
|
568
|
+
action.activity === 'working' || action.activity === 'waiting-input'
|
|
569
|
+
? undefined
|
|
570
|
+
: tab.unseen,
|
|
571
|
+
})),
|
|
572
|
+
}
|
|
573
|
+
case 'mark-tab-unseen':
|
|
574
|
+
return {
|
|
575
|
+
...state,
|
|
576
|
+
tabs: updateTab(state.tabs, action.tabId, (tab) => ({ ...tab, unseen: true })),
|
|
531
577
|
}
|
|
532
578
|
case 'set-tab-error':
|
|
533
579
|
return {
|
|
@@ -555,6 +601,7 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
555
601
|
tabs: updateTab(state.tabs, action.tabId, (tab) => ({
|
|
556
602
|
...tab,
|
|
557
603
|
autoRenameStatus: action.autoRenameStatus ?? tab.autoRenameStatus,
|
|
604
|
+
hidden: action.hidden ?? tab.hidden,
|
|
558
605
|
title: action.title ?? tab.title,
|
|
559
606
|
})),
|
|
560
607
|
}
|
|
@@ -590,7 +637,7 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
590
637
|
}
|
|
591
638
|
const tabs = [...state.tabs.slice(0, insertIndex), newTab, ...state.tabs.slice(insertIndex)]
|
|
592
639
|
|
|
593
|
-
return
|
|
640
|
+
return withActiveTabWorkspace(
|
|
594
641
|
{
|
|
595
642
|
...state,
|
|
596
643
|
activeTabId: newTab.id,
|
|
@@ -621,7 +668,7 @@ export function reduceTabState(state: AppState, action: AppAction): AppState | n
|
|
|
621
668
|
if (!(neighbor != null && neighbor !== '')) {
|
|
622
669
|
return state
|
|
623
670
|
}
|
|
624
|
-
return
|
|
671
|
+
return withActiveTabWorkspace({ ...state, activeTabId: neighbor }, neighbor)
|
|
625
672
|
}
|
|
626
673
|
case 'resize-pane': {
|
|
627
674
|
const resizeGroupId = getGroupIdForTab(state.tabGroupMap, action.tabId)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AppAction
|
|
1
|
+
import type { AppAction } from '../actions'
|
|
2
|
+
import type { AppState, BarSide, BarState, BarWidget } from '../types'
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
boundaryDeltaFromRatio,
|
|
@@ -99,10 +100,10 @@ export function reduceUIState(state: AppState, action: AppAction): AppState | nu
|
|
|
99
100
|
return state
|
|
100
101
|
}
|
|
101
102
|
return { ...state, pendingChords: action.chords }
|
|
102
|
-
case 'toggle-
|
|
103
|
+
case 'toggle-project-bar':
|
|
103
104
|
return {
|
|
104
105
|
...state,
|
|
105
|
-
|
|
106
|
+
projectBar: { ...state.projectBar, visible: !state.projectBar.visible },
|
|
106
107
|
}
|
|
107
108
|
default:
|
|
108
109
|
return null
|
package/src/state/selectors.ts
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { ProjectRecord, SnippetRecord, WorkspaceRecord } from './types'
|
|
2
|
+
|
|
3
|
+
import { type AssistantOption, getAllAssistantOptions } from '../pty/command-registry'
|
|
3
4
|
|
|
4
5
|
export interface BaseRefOption {
|
|
5
|
-
/** Git ref the new
|
|
6
|
+
/** Git ref the new workspace is forked from. */
|
|
6
7
|
ref: string
|
|
7
8
|
label: string
|
|
8
|
-
kind: '
|
|
9
|
-
/**
|
|
9
|
+
kind: 'workspace' | 'branch'
|
|
10
|
+
/** Workspace name, for the 'workspace' kind. */
|
|
10
11
|
detail?: string
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* Ordered, filtered base-ref candidates for the
|
|
15
|
-
* branches checked out in the
|
|
16
|
-
*
|
|
17
|
-
* via a
|
|
18
|
-
* a live
|
|
19
|
-
* so deleted temp
|
|
15
|
+
* Ordered, filtered base-ref candidates for the workspace-create "Base" picker:
|
|
16
|
+
* branches checked out in the project's workspaces first (labelled with the
|
|
17
|
+
* workspace name), then the remaining local branches. A branch already surfaced
|
|
18
|
+
* via a workspace is not repeated. Throwaway `aimux/` branches are skipped unless
|
|
19
|
+
* a live workspace is on them — `git worktree remove` leaves the branch behind,
|
|
20
|
+
* so deleted temp workspaces would otherwise haunt the list as orphan branches.
|
|
20
21
|
*/
|
|
21
22
|
export function buildBaseRefOptions(
|
|
22
|
-
|
|
23
|
+
workspaces: WorkspaceRecord[],
|
|
23
24
|
localBranches: string[],
|
|
24
25
|
query: string
|
|
25
26
|
): BaseRefOption[] {
|
|
26
27
|
const seen = new Set<string>()
|
|
27
28
|
const options: BaseRefOption[] = []
|
|
28
|
-
for (const
|
|
29
|
-
if (
|
|
30
|
-
seen.add(
|
|
29
|
+
for (const workspace of workspaces) {
|
|
30
|
+
if (workspace.branch == null || workspace.branch === '' || seen.has(workspace.branch)) continue
|
|
31
|
+
seen.add(workspace.branch)
|
|
31
32
|
options.push({
|
|
32
|
-
detail:
|
|
33
|
-
kind: '
|
|
34
|
-
label:
|
|
35
|
-
ref:
|
|
33
|
+
detail: workspace.name,
|
|
34
|
+
kind: 'workspace',
|
|
35
|
+
label: workspace.branch,
|
|
36
|
+
ref: workspace.branch,
|
|
36
37
|
})
|
|
37
38
|
}
|
|
38
39
|
for (const branch of localBranches) {
|
|
@@ -45,16 +46,6 @@ export function buildBaseRefOptions(
|
|
|
45
46
|
return options.filter((option) => option.label.toLowerCase().includes(trimmed))
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
/**
|
|
49
|
-
* 0 when the template picker should NOT show the "None" fallback (no assistant
|
|
50
|
-
* was picked — typical for the template shortcut path), 1 otherwise. Shared
|
|
51
|
-
* by the modal reducer, the side-effect that resolves templateId, and the
|
|
52
|
-
* picker UI so the three stay in sync.
|
|
53
|
-
*/
|
|
54
|
-
export function getTemplateNoneOffset(selectedAssistantId: AssistantId | null): 0 | 1 {
|
|
55
|
-
return selectedAssistantId == null ? 0 : 1
|
|
56
|
-
}
|
|
57
|
-
|
|
58
49
|
export function filterAssistants(
|
|
59
50
|
options: AssistantOption[],
|
|
60
51
|
filter: string | null
|
|
@@ -66,18 +57,39 @@ export function filterAssistants(
|
|
|
66
57
|
)
|
|
67
58
|
}
|
|
68
59
|
|
|
69
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Assistants offered by the new-tab picker. One source of truth on purpose: the
|
|
62
|
+
* modal renders this list while `launch-selected-assistant` indexes into it, so
|
|
63
|
+
* a divergence launches the row above or below the one the user highlighted.
|
|
64
|
+
*
|
|
65
|
+
* Chained from `<C-p>`, the prompt is pasted into the assistant and drives the
|
|
66
|
+
* workspace's name — neither of which a plain shell can do — so Terminal is not
|
|
67
|
+
* offered there. A bare `<C-n>` still lists it.
|
|
68
|
+
*/
|
|
69
|
+
export function getNewTabAssistantOptions(
|
|
70
|
+
customCommands: Record<string, string>,
|
|
71
|
+
filter: string | null,
|
|
72
|
+
excludeTerminal: boolean
|
|
73
|
+
): AssistantOption[] {
|
|
74
|
+
const all = getAllAssistantOptions(customCommands)
|
|
75
|
+
return filterAssistants(
|
|
76
|
+
excludeTerminal ? all.filter((option) => option.id !== 'terminal') : all,
|
|
77
|
+
filter
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function filterProjects(projects: ProjectRecord[], filter: string | null): ProjectRecord[] {
|
|
70
82
|
if (!(filter != null && filter !== '')) {
|
|
71
|
-
return
|
|
83
|
+
return projects
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
const lower = filter.toLowerCase()
|
|
75
|
-
return
|
|
76
|
-
(
|
|
77
|
-
|
|
78
|
-
(
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
return projects.filter(
|
|
88
|
+
(project) =>
|
|
89
|
+
project.name.toLowerCase().includes(lower) ||
|
|
90
|
+
(project.projectPath != null &&
|
|
91
|
+
project.projectPath !== '' &&
|
|
92
|
+
project.projectPath.toLowerCase().includes(lower))
|
|
81
93
|
)
|
|
82
94
|
}
|
|
83
95
|
|
package/src/state/store.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AppAction } from './actions'
|
|
2
2
|
|
|
3
3
|
import { clampBarWidth, KNOWN_WIDGET_IDS } from './bars'
|
|
4
4
|
import { reduceAutoCommit } from './reducers/auto-commit-state'
|
|
@@ -6,19 +6,19 @@ import { emptyGitMode, reduceGitModeState } from './reducers/git-mode-state'
|
|
|
6
6
|
import { emptyGitPanel, reduceGitPanelState } from './reducers/git-panel-state'
|
|
7
7
|
import { emptyModal, reduceModalState } from './reducers/modal-state'
|
|
8
8
|
import { reduceMultiRepoState } from './reducers/multi-repo-state'
|
|
9
|
-
import {
|
|
9
|
+
import { reduceProjectState } from './reducers/project-state'
|
|
10
|
+
import { emptySettingsUI, reduceSettingsState } from './reducers/settings-state'
|
|
10
11
|
import { reduceTabState } from './reducers/tab-state'
|
|
11
12
|
import { reduceUIState } from './reducers/ui-state'
|
|
12
13
|
import { filterSnippets } from './selectors'
|
|
13
14
|
import {
|
|
14
|
-
type AppAction,
|
|
15
15
|
type AppState,
|
|
16
16
|
type BarsState,
|
|
17
17
|
EMPTY_AUTO_COMMIT_STATE,
|
|
18
18
|
EMPTY_MULTI_REPO_STATE,
|
|
19
19
|
type GitModeState,
|
|
20
20
|
type GitPaneState,
|
|
21
|
-
type
|
|
21
|
+
type ProjectRecord,
|
|
22
22
|
type SnippetRecord,
|
|
23
23
|
} from './types'
|
|
24
24
|
|
|
@@ -29,8 +29,7 @@ export interface InitialStateOverrides {
|
|
|
29
29
|
gitMode?: Partial<GitModeState>
|
|
30
30
|
gitPane?: Partial<GitPaneState>
|
|
31
31
|
bars?: BarsState
|
|
32
|
-
|
|
33
|
-
worktreeTemplates?: WorktreeTemplate[]
|
|
32
|
+
projectBarVisible?: boolean
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
const DEFAULT_GIT_PANE: GitPaneState = {
|
|
@@ -46,14 +45,26 @@ export const DEFAULT_BARS: BarsState = {
|
|
|
46
45
|
left: {
|
|
47
46
|
visible: true,
|
|
48
47
|
widgets: [
|
|
49
|
-
{ grow: 50, id: '
|
|
48
|
+
{ grow: 50, id: 'projects', visible: true },
|
|
50
49
|
{ grow: 50, id: 'git', visible: true },
|
|
50
|
+
// Opt-in: reachable via the bar's right-click menu ("Show Setup"). Present
|
|
51
|
+
// here so sanitizeBars places it rather than treating it as corruption.
|
|
52
|
+
{ grow: 50, id: 'setup', visible: false },
|
|
51
53
|
],
|
|
52
54
|
width: 28,
|
|
53
55
|
},
|
|
54
56
|
right: { visible: false, widgets: [], width: 40 },
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Persisted widget ids from before the project/workspace rename. Without this
|
|
61
|
+
* the old id is simply unknown, so the widget is pruned, the bar renders empty
|
|
62
|
+
* and the emptied layout is written straight back to aimux.json.
|
|
63
|
+
*
|
|
64
|
+
* ponytail: drop with the other rename shims.
|
|
65
|
+
*/
|
|
66
|
+
const LEGACY_WIDGET_IDS: Record<string, string> = { workspaces: 'projects' }
|
|
67
|
+
|
|
57
68
|
/**
|
|
58
69
|
* Drop widget ids this build cannot render (config written by a newer or
|
|
59
70
|
* patched version) and normalise widths — the only place unknown ids can enter.
|
|
@@ -62,63 +73,82 @@ function sanitizeBars(bars: BarsState): BarsState {
|
|
|
62
73
|
const sanitizeBar = (bar: BarsState[keyof BarsState]): BarsState[keyof BarsState] => ({
|
|
63
74
|
...bar,
|
|
64
75
|
widgets: bar.widgets
|
|
76
|
+
.map((widget) => ({ ...widget, id: LEGACY_WIDGET_IDS[widget.id] ?? widget.id }))
|
|
65
77
|
.filter((widget) => (KNOWN_WIDGET_IDS as readonly string[]).includes(widget.id))
|
|
66
78
|
.map((widget) => ({ ...widget, grow: Math.max(1, Math.round(widget.grow)) })),
|
|
67
79
|
width: clampBarWidth(bar.width),
|
|
68
80
|
})
|
|
69
|
-
|
|
81
|
+
const sanitized: BarsState = { left: sanitizeBar(bars.left), right: sanitizeBar(bars.right) }
|
|
82
|
+
|
|
83
|
+
// A widget can only ever be moved between bars or hidden, never deleted, so
|
|
84
|
+
// one that is in neither bar means the persisted layout was corrupted —
|
|
85
|
+
// which is exactly what the pre-rename `workspaces` id did to older files
|
|
86
|
+
// before LEGACY_WIDGET_IDS existed. Put it back where it ships by default,
|
|
87
|
+
// otherwise the bar renders empty forever and re-saves that emptiness.
|
|
88
|
+
for (const side of ['left', 'right'] as const) {
|
|
89
|
+
for (const fallback of DEFAULT_BARS[side].widgets) {
|
|
90
|
+
const present =
|
|
91
|
+
sanitized.left.widgets.some((w) => w.id === fallback.id) ||
|
|
92
|
+
sanitized.right.widgets.some((w) => w.id === fallback.id)
|
|
93
|
+
if (!present) {
|
|
94
|
+
sanitized[side] = { ...sanitized[side], widgets: [...sanitized[side].widgets, fallback] }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return sanitized
|
|
70
99
|
}
|
|
71
100
|
|
|
72
101
|
export function createInitialState(
|
|
73
102
|
customCommands: Record<string, string> = {},
|
|
74
|
-
|
|
103
|
+
projects: ProjectRecord[] = [],
|
|
75
104
|
snippets: SnippetRecord[] = [],
|
|
76
|
-
|
|
105
|
+
showProjectPicker = false,
|
|
77
106
|
overrides: InitialStateOverrides = {}
|
|
78
107
|
): AppState {
|
|
79
108
|
return {
|
|
80
109
|
activeTabId: null,
|
|
81
110
|
autoCommit: EMPTY_AUTO_COMMIT_STATE,
|
|
82
111
|
bars: sanitizeBars(overrides.bars ?? DEFAULT_BARS),
|
|
83
|
-
|
|
112
|
+
currentProjectId: null,
|
|
84
113
|
customCommands,
|
|
85
|
-
focusMode:
|
|
114
|
+
focusMode: showProjectPicker ? 'command-edit' : 'navigation',
|
|
86
115
|
gitMode: { ...emptyGitMode(), ...overrides.gitMode },
|
|
87
116
|
gitPane: { ...DEFAULT_GIT_PANE, ...overrides.gitPane },
|
|
88
117
|
gitPanel: emptyGitPanel(),
|
|
89
|
-
|
|
118
|
+
lastActiveTabByWorkspace: {},
|
|
90
119
|
layout: {
|
|
91
120
|
terminalCols: DEFAULT_TERMINAL_COLS,
|
|
92
121
|
terminalRows: DEFAULT_TERMINAL_ROWS,
|
|
93
122
|
},
|
|
94
123
|
layoutTrees: {},
|
|
95
|
-
modal:
|
|
124
|
+
modal: showProjectPicker
|
|
96
125
|
? {
|
|
97
126
|
cursorPos: 0,
|
|
98
127
|
editBuffer: '',
|
|
128
|
+
projectTargetId: null,
|
|
99
129
|
selectedIndex: 0,
|
|
100
|
-
|
|
101
|
-
type: 'session-picker',
|
|
130
|
+
type: 'project-picker',
|
|
102
131
|
}
|
|
103
132
|
: emptyModal(),
|
|
104
133
|
multiRepo: EMPTY_MULTI_REPO_STATE,
|
|
105
134
|
pendingChords: null,
|
|
106
|
-
|
|
107
|
-
visible: overrides.
|
|
135
|
+
projectBar: {
|
|
136
|
+
visible: overrides.projectBarVisible ?? true,
|
|
108
137
|
},
|
|
109
|
-
|
|
110
|
-
|
|
138
|
+
projects,
|
|
139
|
+
projectStatuses: {},
|
|
140
|
+
settings: emptySettingsUI(),
|
|
111
141
|
snippets,
|
|
112
142
|
tabGroupMap: {},
|
|
113
143
|
tabs: [],
|
|
114
|
-
|
|
115
|
-
|
|
144
|
+
workspaceActivity: {},
|
|
145
|
+
workspaceDivergence: {},
|
|
116
146
|
}
|
|
117
147
|
}
|
|
118
148
|
|
|
119
149
|
export function appReducer(state: AppState, action: AppAction): AppState {
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
150
|
+
const projectState = reduceProjectState(state, action)
|
|
151
|
+
if (projectState) return projectState
|
|
122
152
|
|
|
123
153
|
const tabState = reduceTabState(state, action)
|
|
124
154
|
if (tabState) return tabState
|
|
@@ -141,6 +171,9 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
141
171
|
const multiRepoState = reduceMultiRepoState(state, action)
|
|
142
172
|
if (multiRepoState) return multiRepoState
|
|
143
173
|
|
|
174
|
+
const settingsState = reduceSettingsState(state, action)
|
|
175
|
+
if (settingsState) return settingsState
|
|
176
|
+
|
|
144
177
|
switch (action.type) {
|
|
145
178
|
case 'set-snippets':
|
|
146
179
|
return { ...state, snippets: action.snippets }
|