@brimveyn/aimux-config 0.8.5 → 0.10.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/src/actions.ts CHANGED
@@ -20,16 +20,17 @@ function r(
20
20
  export const nextTab: KeyResult = r([{ delta: 1, type: 'move-active-tab' }])
21
21
  export const prevTab: KeyResult = r([{ delta: -1, type: 'move-active-tab' }])
22
22
 
23
- export const newTab: KeyResult = r(
24
- [{ type: 'open-new-tab-modal' }],
25
- [],
26
- 'modal.new-tab.command-edit'
27
- )
23
+ /**
24
+ * Routed through an effect rather than opening the modal directly: tabs only
25
+ * exist inside a workspace, and whether the project has one is the app's
26
+ * business, not the keymap's. The effect opens the picker or explains why not.
27
+ */
28
+ export const newTab: KeyResult = r([], [{ type: 'open-new-tab' }])
28
29
  export const renameTab: KeyResult = r([{ type: 'open-rename-tab-modal' }], [], 'modal.rename-tab')
29
- export const sessionPicker: KeyResult = r(
30
- [{ type: 'open-session-picker' }],
30
+ export const projectPicker: KeyResult = r(
31
+ [{ type: 'open-project-picker' }],
31
32
  [],
32
- 'modal.session-picker.filtering'
33
+ 'modal.project-picker.filtering'
33
34
  )
34
35
  export const snippetPicker: KeyResult = r(
35
36
  [{ type: 'open-snippet-picker' }],
@@ -51,9 +52,15 @@ export const openFlashJump: KeyResult = r(
51
52
  'modal.flash-jump'
52
53
  )
53
54
 
54
- export const toggleSidebar: KeyResult = r([{ type: 'toggle-sidebar' }])
55
- export const toggleGitPane: KeyResult = r([{ type: 'toggle-git-pane' }])
56
- export const toggleSessionBar: KeyResult = r([{ type: 'toggle-session-bar' }])
55
+ export function toggleBar(side: 'left' | 'right'): KeyResult {
56
+ return r([{ side, type: 'toggle-bar' }])
57
+ }
58
+ export const toggleSidebar: KeyResult = toggleBar('left')
59
+ export function toggleWidget(widgetId: string): KeyResult {
60
+ return r([{ type: 'toggle-widget', widgetId }])
61
+ }
62
+ export const toggleGitPane: KeyResult = toggleWidget('git')
63
+ export const toggleProjectBar: KeyResult = r([{ type: 'toggle-project-bar' }])
57
64
  export const toggleAIUsage: ActionFn = (ctx: ModeContext) => {
58
65
  if (ctx.state.modal.type === 'ai-usage') {
59
66
  return r([{ type: 'close-modal' }], [], 'navigation')
@@ -61,17 +68,10 @@ export const toggleAIUsage: ActionFn = (ctx: ModeContext) => {
61
68
  return r([{ type: 'open-ai-usage-modal' }], [], 'modal.ai-usage')
62
69
  }
63
70
 
64
- export function setGitPaneMode(mode: 'embedded' | 'pane'): KeyResult {
65
- return r([{ mode, type: 'set-git-pane-mode' }])
66
- }
67
-
68
- export function setGitPanePosition(position: 'top' | 'bottom' | 'left' | 'right'): KeyResult {
69
- return r([{ position, type: 'set-git-pane-position' }])
70
- }
71
71
  export const enterGitMode: KeyResult = r([{ type: 'enter-git-mode' }], [], 'git-mode')
72
72
 
73
- export function switchSessionByIndex(index: number): KeyResult {
74
- return r([], [{ index, type: 'switch-session-by-index' }])
73
+ export function switchProjectByIndex(index: number): KeyResult {
74
+ return r([], [{ index, type: 'switch-project-by-index' }])
75
75
  }
76
76
 
77
77
  export function switchTabByIndex(index: number): KeyResult {
@@ -135,16 +135,24 @@ export function reorderTab(delta: number): KeyResult {
135
135
  return r([{ delta, type: 'reorder-active-tab' }])
136
136
  }
137
137
 
138
- export function reorderSession(delta: number): KeyResult {
139
- return r([{ delta, type: 'reorder-active-session' }])
138
+ export function reorderProject(delta: number): KeyResult {
139
+ return r([{ delta, type: 'reorder-active-project' }])
140
+ }
141
+
142
+ export function resizeBar(side: 'left' | 'right', delta: number): KeyResult {
143
+ return r([{ delta, side, type: 'resize-bar' }])
140
144
  }
141
145
 
142
146
  export function resizeSidebar(delta: number): KeyResult {
143
- return r([{ delta, type: 'resize-sidebar' }])
147
+ return resizeBar('left', delta)
148
+ }
149
+
150
+ export function resizeWidget(widgetId: string, delta: number): KeyResult {
151
+ return r([{ delta, type: 'resize-widget', widgetId }])
144
152
  }
145
153
 
146
154
  export function resizeGitPane(delta: number): KeyResult {
147
- return r([{ delta, type: 'resize-git-pane' }])
155
+ return resizeWidget('git', delta)
148
156
  }
149
157
 
150
158
  export function resizeGitDiffPane(delta: number): ActionFn {
@@ -192,70 +200,20 @@ export function moveModalSelectionWithPreview(
192
200
  // Modal-specific actions
193
201
  // ---------------------------------------------------------------------------
194
202
 
195
- export const launchSelectedAssistant: ActionFn = (ctx: ModeContext) => {
196
- if (ctx.state.modal.type === 'new-tab' && ctx.state.modal.step === 'assistant') {
197
- return r([{ type: 'select-new-tab-assistant' }])
198
- }
199
- if (
200
- ctx.state.modal.type === 'new-tab' &&
201
- ctx.state.modal.step === 'worktree' &&
202
- ctx.state.modal.createWorktree
203
- ) {
204
- return r([{ type: 'enter-new-tab-worktree-create' }], [{ type: 'load-new-tab-base-branches' }])
205
- }
206
- return r([], [{ type: 'launch-selected-assistant' }])
207
- }
203
+ export const launchSelectedAssistant: KeyResult = r([], [{ type: 'launch-selected-assistant' }])
208
204
 
209
- export const toggleNewTabWorktree: KeyResult = r([{ type: 'toggle-new-tab-worktree' }])
210
-
211
- export const deleteSelectedWorktree: ActionFn = (ctx: ModeContext) => {
205
+ export const confirmWorkspaceDeleteModal: ActionFn = (ctx: ModeContext) => {
212
206
  const modal = ctx.state.modal
213
- const sessionId = ctx.state.currentSessionId
214
- if (modal.type !== 'new-tab' || modal.step !== 'worktree' || modal.createWorktree) return null
215
- if (!(sessionId != null && sessionId !== '')) return null
216
- const session = ctx.state.sessions.find((entry) => entry.id === sessionId)
217
- const worktree = session?.worktrees?.[modal.selectedIndex]
218
- if (!worktree) return null
219
- // First attempt is never forced — a recoverable failure opens the confirm
220
- // dialog (see set-new-tab-worktree-delete-prompt), where confirmDeleteWorktree
221
- // retries with force.
222
- return r(
223
- [{ index: modal.selectedIndex, type: 'set-modal-selection-index' }],
224
- [{ force: false, sessionId, type: 'delete-worktree', worktreeId: worktree.id }]
225
- )
226
- }
227
-
228
- export const confirmDeleteWorktree: ActionFn = (ctx: ModeContext) => {
229
- const modal = ctx.state.modal
230
- const sessionId = ctx.state.currentSessionId
231
- if (modal.type !== 'new-tab' || modal.worktreeDeletePrompt == null) return null
232
- if (!(sessionId != null && sessionId !== '')) return null
233
- const { worktreeId } = modal.worktreeDeletePrompt
234
- return r(
235
- [{ prompt: null, type: 'set-new-tab-worktree-delete-prompt' }],
236
- [{ force: true, sessionId, type: 'delete-worktree', worktreeId }],
237
- 'modal.new-tab.command-edit'
238
- )
239
- }
240
-
241
- export const cancelDeleteWorktree: KeyResult = r(
242
- [{ prompt: null, type: 'set-new-tab-worktree-delete-prompt' }],
243
- [],
244
- 'modal.new-tab.command-edit'
245
- )
246
-
247
- export const confirmWorktreeDeleteModal: ActionFn = (ctx: ModeContext) => {
248
- const modal = ctx.state.modal
249
- if (modal.type !== 'worktree-delete-confirm') return null
207
+ if (modal.type !== 'workspace-delete-confirm') return null
250
208
  return r(
251
209
  [{ type: 'close-modal' }],
252
210
  [
253
211
  {
254
212
  closeTabs: modal.closeTabs,
255
213
  force: modal.force,
256
- sessionId: modal.sessionId,
257
- type: 'delete-worktree',
258
- worktreeId: modal.worktreeId,
214
+ projectId: modal.projectId,
215
+ type: 'delete-workspace',
216
+ workspaceId: modal.workspaceId,
259
217
  },
260
218
  ],
261
219
  'navigation'
@@ -265,20 +223,52 @@ export const confirmWorktreeDeleteModal: ActionFn = (ctx: ModeContext) => {
265
223
  export const cancelCommandEdit = (returnTo: KeyResult['transition']): KeyResult =>
266
224
  r([{ type: 'cancel-command-edit' }], [], returnTo)
267
225
 
268
- export const confirmSelectedSession: KeyResult = r([], [{ type: 'confirm-selected-session' }])
226
+ export const confirmSelectedProject: KeyResult = r([], [{ type: 'confirm-selected-project' }])
269
227
 
270
- export const openCreateSessionModal: KeyResult = r(
271
- [{ returnToSessionPicker: true, type: 'open-create-session-modal' }],
228
+ export const openCreateProjectModal: KeyResult = r(
229
+ [{ returnToProjectPicker: true, type: 'open-create-project-modal' }],
272
230
  [],
273
- 'modal.create-session'
231
+ 'modal.create-project'
274
232
  )
275
233
 
276
- export const openRenameSelectedSession: KeyResult = r(
234
+ export const createWorkspaceModal: KeyResult = r(
235
+ [{ type: 'open-create-workspace-modal' }],
236
+ [{ type: 'load-create-workspace-base-branches' }],
237
+ 'modal.create-workspace'
238
+ )
239
+
240
+ export const switchCreateWorkspaceField: KeyResult = r([{ type: 'switch-create-workspace-field' }])
241
+
242
+ /**
243
+ * An empty prompt is not a workspace: it names nothing, branches nothing and
244
+ * gives the assistant nothing to do, so Enter is simply inert until it is typed.
245
+ */
246
+ export const confirmCreateWorkspace: ActionFn = (ctx: ModeContext) => {
247
+ const { modal } = ctx.state
248
+ if (modal.type === 'create-workspace' && modal.prompt.trim() === '') return r([])
249
+ return r([], [{ type: 'create-workspace' }])
250
+ }
251
+
252
+ /**
253
+ * `Enter` creates, so the prompt needs another way to break a line. Inverted
254
+ * from the commit modal on purpose: there the body is the whole point, here
255
+ * most prompts are one line and creating must stay the cheapest keystroke.
256
+ */
257
+ export const createWorkspaceNewline: ActionFn = (ctx: ModeContext) => {
258
+ const { modal } = ctx.state
259
+ if (modal.type !== 'create-workspace') return r([])
260
+ if (modal.activeField !== 'prompt') return r([])
261
+ return r([{ char: '\n', type: 'update-command-edit' }])
262
+ }
263
+
264
+ export const createWorkspaceEscape: KeyResult = r([{ type: 'close-modal' }], [], 'navigation')
265
+
266
+ export const openRenameSelectedProject: KeyResult = r(
277
267
  [],
278
- [{ type: 'open-rename-selected-session' }]
268
+ [{ type: 'open-rename-selected-project' }]
279
269
  )
280
270
 
281
- export const deleteSelectedSession: KeyResult = r([], [{ type: 'delete-selected-session' }])
271
+ export const deleteSelectedProject: KeyResult = r([], [{ type: 'delete-selected-project' }])
282
272
 
283
273
  export const openSnippetEditor: KeyResult = r(
284
274
  [{ type: 'open-snippet-editor' }],
@@ -329,20 +319,20 @@ export function previewTheme(delta: 1 | -1): KeyResult {
329
319
  export const toggleTransparent: KeyResult = r([], [{ type: 'toggle-transparent' }])
330
320
  export const toggleMode: KeyResult = r([], [{ type: 'toggle-mode' }])
331
321
 
332
- export const switchField: KeyResult = r([{ type: 'switch-create-session-field' }])
322
+ export const switchField: KeyResult = r([{ type: 'switch-create-project-field' }])
333
323
  export const selectDirectory: KeyResult = r([{ type: 'select-directory' }])
334
324
 
335
- export const backToSessionPicker: KeyResult = r(
336
- [{ type: 'open-session-picker' }],
325
+ export const backToProjectPicker: KeyResult = r(
326
+ [{ type: 'open-project-picker' }],
337
327
  [],
338
- 'modal.session-picker.filtering'
328
+ 'modal.project-picker.filtering'
339
329
  )
340
330
 
341
- export const createSessionEscape: ActionFn = (ctx: ModeContext) => {
342
- if (ctx.state.modal.type === 'create-session' && !ctx.state.modal.returnToSessionPicker) {
331
+ export const createProjectEscape: ActionFn = (ctx: ModeContext) => {
332
+ if (ctx.state.modal.type === 'create-project' && !ctx.state.modal.returnToProjectPicker) {
343
333
  return r([{ type: 'close-modal' }], [], 'navigation')
344
334
  }
345
- return r([{ type: 'open-session-picker' }], [], 'modal.session-picker.filtering')
335
+ return r([{ type: 'open-project-picker' }], [], 'modal.project-picker.filtering')
346
336
  }
347
337
 
348
338
  export const backToSnippetPicker: KeyResult = r(
@@ -386,10 +376,10 @@ export const confirmUpdateSelection: KeyResult = r(
386
376
  )
387
377
 
388
378
  // ---------------------------------------------------------------------------
389
- // Worktree-move modal
379
+ // Workspace-move modal
390
380
  // ---------------------------------------------------------------------------
391
381
 
392
- export const openWorktreeMove: ActionFn = (ctx: ModeContext) => {
382
+ export const openWorkspaceMove: ActionFn = (ctx: ModeContext) => {
393
383
  if (ctx.state.gitMode.headOffset > 0) {
394
384
  return r([
395
385
  {
@@ -398,35 +388,35 @@ export const openWorktreeMove: ActionFn = (ctx: ModeContext) => {
398
388
  },
399
389
  ])
400
390
  }
401
- const session = ctx.state.sessions.find((entry) => entry.id === ctx.state.currentSessionId)
402
- const worktrees = session?.worktrees ?? []
403
- // From git mode, the source is the active worktree (the one being reviewed).
404
- const source = worktrees.find((w) => w.id === session?.activeWorktreeId) ?? worktrees[0]
391
+ const project = ctx.state.projects.find((entry) => entry.id === ctx.state.currentProjectId)
392
+ const workspaces = project?.workspaces ?? []
393
+ // From git mode, the source is the active workspace (the one being reviewed).
394
+ const source = workspaces.find((w) => w.id === project?.activeWorkspaceId) ?? workspaces[0]
405
395
  const hasBranch = source != null && source.branch != null && source.branch !== ''
406
- const others = worktrees.filter((w) => w.id !== source?.id)
396
+ const others = workspaces.filter((w) => w.id !== source?.id)
407
397
  if (!hasBranch || source == null || others.length < 1) {
408
- return r([{ message: 'no other worktree to move into', type: 'git-mode-set-message' }])
398
+ return r([{ message: 'no other workspace to move into', type: 'git-mode-set-message' }])
409
399
  }
410
400
  // Overlay: no mode transition — deriveModeId routes input to the picker and
411
401
  // focusMode stays 'git' so the git view remains mounted underneath.
412
402
  return r(
413
- [{ sourceWorktreeId: source.id, type: 'open-worktree-move-modal' }],
414
- [{ type: 'load-worktree-move-stats' }]
403
+ [{ sourceWorkspaceId: source.id, type: 'open-workspace-move-modal' }],
404
+ [{ type: 'load-workspace-move-stats' }]
415
405
  )
416
406
  }
417
407
 
418
- export const toggleWorktreeMoveDelete: KeyResult = r([{ type: 'toggle-worktree-move-delete' }])
408
+ export const toggleWorkspaceMoveDelete: KeyResult = r([{ type: 'toggle-workspace-move-delete' }])
419
409
 
420
- export const confirmWorktreeMove: ActionFn = (ctx: ModeContext) => {
410
+ export const confirmWorkspaceMove: ActionFn = (ctx: ModeContext) => {
421
411
  const modal = ctx.state.modal
422
- const session = ctx.state.sessions.find((entry) => entry.id === ctx.state.currentSessionId)
423
- const worktrees = session?.worktrees ?? []
424
- const sourceId = modal.type === 'worktree-move' ? modal.sourceWorktreeId : undefined
425
- const source = worktrees.find((w) => w.id === sourceId)
426
- const targets = worktrees.filter((w) => w.id !== sourceId)
427
- const selectedIndex = modal.type === 'worktree-move' ? modal.selectedIndex : 0
412
+ const project = ctx.state.projects.find((entry) => entry.id === ctx.state.currentProjectId)
413
+ const workspaces = project?.workspaces ?? []
414
+ const sourceId = modal.type === 'workspace-move' ? modal.sourceWorkspaceId : undefined
415
+ const source = workspaces.find((w) => w.id === sourceId)
416
+ const targets = workspaces.filter((w) => w.id !== sourceId)
417
+ const selectedIndex = modal.type === 'workspace-move' ? modal.selectedIndex : 0
428
418
  const target = targets[selectedIndex]
429
- if (!target || !session || !source || modal.type !== 'worktree-move') {
419
+ if (!target || !project || !source || modal.type !== 'workspace-move') {
430
420
  return r([{ type: 'close-modal' }])
431
421
  }
432
422
  // Overlay close keeps focusMode 'git' (see close-modal reducer), so the move
@@ -436,10 +426,10 @@ export const confirmWorktreeMove: ActionFn = (ctx: ModeContext) => {
436
426
  [
437
427
  {
438
428
  deleteSource: modal.deleteSource,
439
- sessionId: session.id,
440
- sourceWorktreeId: source.id,
441
- targetWorktreeId: target.id,
442
- type: 'move-worktree',
429
+ projectId: project.id,
430
+ sourceWorkspaceId: source.id,
431
+ targetWorkspaceId: target.id,
432
+ type: 'move-workspace',
443
433
  },
444
434
  ]
445
435
  )
@@ -447,18 +437,18 @@ export const confirmWorktreeMove: ActionFn = (ctx: ModeContext) => {
447
437
 
448
438
  // Confirms the dialog opened after a recoverable move failure: re-runs the
449
439
  // move with the flag matching the failure (stash the target / keep conflicts).
450
- export const confirmWorktreeMoveRetry: ActionFn = (ctx: ModeContext) => {
440
+ export const confirmWorkspaceMoveRetry: ActionFn = (ctx: ModeContext) => {
451
441
  const modal = ctx.state.modal
452
- if (modal.type !== 'worktree-move-confirm') return null
442
+ if (modal.type !== 'workspace-move-confirm') return null
453
443
  return r(
454
444
  [{ type: 'close-modal' }],
455
445
  [
456
446
  {
457
447
  deleteSource: modal.deleteSource,
458
- sessionId: modal.sessionId,
459
- sourceWorktreeId: modal.sourceWorktreeId,
460
- targetWorktreeId: modal.targetWorktreeId,
461
- type: 'move-worktree',
448
+ projectId: modal.projectId,
449
+ sourceWorkspaceId: modal.sourceWorkspaceId,
450
+ targetWorkspaceId: modal.targetWorkspaceId,
451
+ type: 'move-workspace',
462
452
  ...(modal.variant === 'stash-target' ? { stashTarget: true } : { keepConflicts: true }),
463
453
  },
464
454
  ],
@@ -480,9 +470,11 @@ export const closePane: ActionFn = (ctx: ModeContext) => {
480
470
  }
481
471
 
482
472
  // Navigation-specific dynamic actions
473
+ // Reveals whichever bar hosts the project list — it is not pinned to the left.
483
474
  export const ctrlZSidebar: ActionFn = (ctx: ModeContext) => {
484
- if (!ctx.state.sidebar.visible) {
485
- return r([{ type: 'toggle-sidebar' }])
475
+ const side = ctx.state.bars.left.widgets.some((w) => w.id === 'projects') ? 'left' : 'right'
476
+ if (!ctx.state.bars[side].visible) {
477
+ return r([{ side, type: 'toggle-bar' }])
486
478
  }
487
479
  return r([])
488
480
  }
@@ -498,23 +490,23 @@ export const leaveTerminalInput: KeyResult = r(
498
490
  'navigation'
499
491
  )
500
492
 
501
- export const toggleSidebarFromInput: KeyResult = r([{ type: 'toggle-sidebar' }])
493
+ export const toggleSidebarFromInput: KeyResult = toggleBar('left')
502
494
 
503
- // Session name modal
504
- export const confirmSessionRename: ActionFn = (ctx: ModeContext) => {
495
+ // Project name modal
496
+ export const confirmProjectRename: ActionFn = (ctx: ModeContext) => {
505
497
  const trimmed = (ctx.state.modal.editBuffer ?? '').trim()
506
- const sessionId = ctx.state.modal.sessionTargetId
498
+ const projectId = ctx.state.modal.projectTargetId
507
499
  const returnToPicker =
508
- ctx.state.modal.type === 'session-name' ? ctx.state.modal.returnToSessionPicker : true
500
+ ctx.state.modal.type === 'project-name' ? ctx.state.modal.returnToProjectPicker : true
509
501
  const closeAction: AppAction = returnToPicker
510
- ? { type: 'open-session-picker' }
502
+ ? { type: 'open-project-picker' }
511
503
  : { type: 'close-modal' }
512
504
  const transition: KeyResult['transition'] = returnToPicker
513
- ? 'modal.session-picker.filtering'
505
+ ? 'modal.project-picker.filtering'
514
506
  : 'navigation'
515
507
  const effects: KeyResult['effects'] =
516
- trimmed && sessionId != null && sessionId !== ''
517
- ? [{ name: trimmed, sessionId, type: 'rename-session' }]
508
+ trimmed && projectId != null && projectId !== ''
509
+ ? [{ name: trimmed, projectId, type: 'rename-project' }]
518
510
  : []
519
511
  return r([closeAction], effects, transition)
520
512
  }
@@ -522,7 +514,7 @@ export const confirmSessionRename: ActionFn = (ctx: ModeContext) => {
522
514
  // Rename tab modal
523
515
  export const confirmRenameTab: ActionFn = (ctx: ModeContext) => {
524
516
  const trimmed = (ctx.state.modal.editBuffer ?? '').trim()
525
- const tabId = ctx.state.modal.sessionTargetId
517
+ const tabId = ctx.state.modal.projectTargetId
526
518
  const actions: KeyResult['actions'] = []
527
519
  const effects: KeyResult['effects'] = []
528
520
  if (trimmed && tabId != null && tabId !== '') {
@@ -532,27 +524,33 @@ export const confirmRenameTab: ActionFn = (ctx: ModeContext) => {
532
524
  return r(actions, effects, 'navigation')
533
525
  }
534
526
 
535
- // Rename worktree modal
536
- export const confirmRenameWorktree: ActionFn = (ctx: ModeContext) => {
527
+ // Rename workspace modal
528
+ export const confirmRenameWorkspace: ActionFn = (ctx: ModeContext) => {
537
529
  const { modal } = ctx.state
538
530
  const trimmed = (modal.editBuffer ?? '').trim()
539
- const worktreeId = modal.sessionTargetId
540
- const sessionId = modal.type === 'rename-worktree' ? modal.worktreeSessionId : null
531
+ const workspaceId = modal.projectTargetId
532
+ const projectId = modal.type === 'rename-workspace' ? modal.workspaceProjectId : null
541
533
  const actions: KeyResult['actions'] = []
542
- if (trimmed && worktreeId != null && worktreeId !== '' && sessionId != null && sessionId !== '') {
534
+ if (
535
+ trimmed &&
536
+ workspaceId != null &&
537
+ workspaceId !== '' &&
538
+ projectId != null &&
539
+ projectId !== ''
540
+ ) {
543
541
  actions.push({
544
542
  patch: { name: trimmed },
545
- sessionId,
546
- type: 'update-worktree-record',
547
- worktreeId,
543
+ projectId,
544
+ type: 'update-workspace-record',
545
+ workspaceId,
548
546
  })
549
547
  }
550
548
  actions.push({ type: 'close-modal' })
551
549
  return r(actions, [], 'navigation')
552
550
  }
553
551
 
554
- // Create session modal
555
- export const confirmCreateSession: ActionFn = (ctx: ModeContext) => {
552
+ // Create project modal
553
+ export const confirmCreateProject: ActionFn = (ctx: ModeContext) => {
556
554
  const modal = ctx.state.modal as {
557
555
  activeField?: string
558
556
  editBuffer?: string
@@ -565,26 +563,26 @@ export const confirmCreateSession: ActionFn = (ctx: ModeContext) => {
565
563
 
566
564
  const trimmed = (modal.editBuffer ?? '').trim()
567
565
  const projectPath = modal.pendingProjectPath ?? undefined
568
- const sessionName = trimmed || getDefaultSessionName(projectPath)
569
- if (sessionName) {
566
+ const projectName = trimmed || getDefaultProjectName(projectPath)
567
+ if (projectName) {
570
568
  return r(
571
569
  [{ type: 'close-modal' }],
572
- [{ name: sessionName, projectPath, type: 'create-session' }],
570
+ [{ name: projectName, projectPath, type: 'create-project' }],
573
571
  'navigation'
574
572
  )
575
573
  }
576
574
  return r([{ type: 'close-modal' }], [], 'navigation')
577
575
  }
578
576
 
579
- function getDefaultSessionName(projectPath?: string): string {
577
+ function getDefaultProjectName(projectPath?: string): string {
580
578
  if (!(projectPath != null && projectPath !== '')) return ''
581
579
  const segments = projectPath.split('/').filter(Boolean)
582
580
  return segments.at(-1) ?? ''
583
581
  }
584
582
 
585
- // Session picker escape (conditional)
586
- export const sessionPickerEscape: ActionFn = (ctx: ModeContext) => {
587
- if (!(ctx.state.currentSessionId != null && ctx.state.currentSessionId !== '')) return null
583
+ // Project picker escape (conditional)
584
+ export const projectPickerEscape: ActionFn = (ctx: ModeContext) => {
585
+ if (!(ctx.state.currentProjectId != null && ctx.state.currentProjectId !== '')) return null
588
586
  return r([{ type: 'close-modal' }], [], 'navigation')
589
587
  }
590
588
 
@@ -824,9 +822,9 @@ export const gitCommitOpen: ActionFn = (ctx: ModeContext) => {
824
822
  },
825
823
  ])
826
824
  }
827
- const sessionId = ctx.state.currentSessionId ?? undefined
825
+ const projectId = ctx.state.currentProjectId ?? undefined
828
826
  return r(
829
- [...clearPendingDelete(ctx), { sessionId, type: 'open-git-commit-modal' }],
827
+ [...clearPendingDelete(ctx), { projectId, type: 'open-git-commit-modal' }],
830
828
  [],
831
829
  'modal.git-commit'
832
830
  )
@@ -885,13 +883,13 @@ export const gitCommitEnterConfirm: ActionFn = (ctx: ModeContext) => {
885
883
  if (hasTitle) {
886
884
  return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
887
885
  }
888
- const sessionId = ctx.state.currentSessionId
889
- if (!(sessionId != null && sessionId !== '')) {
886
+ const projectId = ctx.state.currentProjectId
887
+ if (!(projectId != null && projectId !== '')) {
890
888
  return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
891
889
  }
892
890
  return r(
893
- [{ sessionId, type: 'git-commit-enter-generating' }],
894
- [{ sessionId, type: 'generate-auto-commit-now' }],
891
+ [{ projectId, type: 'git-commit-enter-generating' }],
892
+ [{ projectId, type: 'generate-auto-commit-now' }],
895
893
  'modal.git-commit.generating'
896
894
  )
897
895
  }
@@ -903,10 +901,10 @@ export const gitCommitLeaveConfirm: KeyResult = r(
903
901
  )
904
902
 
905
903
  export const gitCommitLeaveGenerating: ActionFn = (ctx: ModeContext) => {
906
- const sessionId = ctx.state.currentSessionId
904
+ const projectId = ctx.state.currentProjectId
907
905
  const actionsList: AppAction[] = [{ type: 'git-commit-leave-generating' }]
908
- if (sessionId != null && sessionId !== '') {
909
- actionsList.push({ sessionId, type: 'auto-commit-clear' })
906
+ if (projectId != null && projectId !== '') {
907
+ actionsList.push({ projectId, type: 'auto-commit-clear' })
910
908
  }
911
909
  return r(actionsList, [], 'modal.git-commit')
912
910
  }
@@ -943,5 +941,105 @@ export const gitCommitReturnKey: ActionFn = (ctx: ModeContext) => {
943
941
  if (modal.type === 'git-commit' && modal.activeField === 'body') {
944
942
  return r([{ char: '\n', type: 'update-command-edit' }])
945
943
  }
946
- return r([{ type: 'switch-create-session-field' }])
944
+ return r([{ type: 'switch-create-project-field' }])
945
+ }
946
+
947
+ // ---------------------------------------------------------------------------
948
+ // Settings screen
949
+ // ---------------------------------------------------------------------------
950
+
951
+ export const enterSettings: KeyResult = r([{ type: 'enter-settings' }], [], 'settings')
952
+ export const exitSettings: KeyResult = r([{ type: 'exit-settings' }], [], 'navigation')
953
+
954
+ export function focusSettingsPane(pane: 'nav' | 'rows'): KeyResult {
955
+ return r([{ pane, type: 'settings-focus-pane' }])
956
+ }
957
+
958
+ export function moveSettingsSelection(delta: -1 | 1): KeyResult {
959
+ return r([{ delta, type: 'settings-move-selection' }])
960
+ }
961
+
962
+ /**
963
+ * One key for every kind of row: it toggles a checkbox, advances an enum, or runs
964
+ * whatever the row does. Which one is the row's business, not the keymap's — so
965
+ * there is no per-kind binding to keep in sync with the schema.
966
+ */
967
+ export const activateSettingsRow: ActionFn = (ctx: ModeContext) => {
968
+ if (ctx.state.settings.pane !== 'rows') return r([{ pane: 'rows', type: 'settings-focus-pane' }])
969
+ return r([], [{ type: 'activate-settings-row' }])
970
+ }
971
+
972
+ export function adjustSettingsRow(delta: 1 | -1): KeyResult {
973
+ return r([], [{ delta, type: 'adjust-settings-row' }])
947
974
  }
975
+
976
+ export const resetSettingsRow: KeyResult = r([], [{ type: 'reset-settings-row' }])
977
+
978
+ /**
979
+ * Closing a modal the settings screen opened. `closeModal` declares `navigation`,
980
+ * which is where every other modal goes and is not where these go — the screen is
981
+ * still drawn behind them, and `returnTo` puts the focus back on it.
982
+ */
983
+ export const closeSettingsModal: KeyResult = r([{ type: 'close-modal' }], [], 'settings')
984
+
985
+ export const openSettingsSearch: KeyResult = r(
986
+ [{ type: 'open-settings-search' }],
987
+ [],
988
+ 'modal.settings-search.filtering'
989
+ )
990
+
991
+ /** Jumps the screen to the highlighted setting, wherever it lives. */
992
+ export const confirmSettingsSearch: KeyResult = r(
993
+ [],
994
+ [{ type: 'confirm-settings-search' }],
995
+ 'settings'
996
+ )
997
+
998
+ /**
999
+ * The value is carried in the effect rather than read from the store by it: the
1000
+ * `close-modal` action runs first and clears the buffer it would have read.
1001
+ */
1002
+ export const confirmSettingText: ActionFn = (ctx: ModeContext) => {
1003
+ const modal = ctx.state.modal
1004
+ if (modal.type !== 'setting-text') return r([{ type: 'close-modal' }], [], 'settings')
1005
+ return r(
1006
+ [{ type: 'close-modal' }],
1007
+ [{ settingId: modal.settingId, type: 'commit-setting-text', value: modal.editBuffer ?? '' }],
1008
+ 'settings'
1009
+ )
1010
+ }
1011
+
1012
+ // ---------------------------------------------------------------------------
1013
+ // Deprecated aliases — 0.8.x names kept working for one release.
1014
+ //
1015
+ // The project/workspace/worktree rename moved every one of these. Aliases only
1016
+ // exist where the old name still has a target: `toggleNewTabWorktree`,
1017
+ // `deleteSelectedWorktree` and `confirmDeleteWorktree` were deleted outright
1018
+ // when <C-n> stopped creating worktrees, so a config referencing them is a
1019
+ // compile error on purpose.
1020
+ //
1021
+ // ponytail: drop these with the other rename shims.
1022
+ // ---------------------------------------------------------------------------
1023
+
1024
+ /** @deprecated renamed to `projectPicker`. */
1025
+ export const sessionPicker = projectPicker
1026
+ /** @deprecated renamed to `openCreateProjectModal`. */
1027
+ export const openCreateSessionModal = openCreateProjectModal
1028
+ /** @deprecated renamed to `createProjectEscape`. */
1029
+ export const createSessionEscape = createProjectEscape
1030
+ /** @deprecated renamed to `confirmCreateProject`. */
1031
+ export const confirmCreateSession = confirmCreateProject
1032
+ /** @deprecated renamed to `reorderProject`. */
1033
+ export const reorderSession = reorderProject
1034
+ /** @deprecated renamed to `switchProjectByIndex`. */
1035
+ export const switchSessionByIndex = switchProjectByIndex
1036
+ /** @deprecated renamed to `toggleProjectBar`. */
1037
+ export const toggleSessionBar = toggleProjectBar
1038
+ /** @deprecated renamed to `openWorkspaceMove`. */
1039
+ export const openWorktreeMove = openWorkspaceMove
1040
+ /** @deprecated renamed to `toggleWorkspaceMoveDelete`. */
1041
+ export const toggleWorktreeMoveDelete = toggleWorkspaceMoveDelete
1042
+ /** @deprecated renamed to `confirmWorkspaceMove`. */
1043
+ export const confirmWorktreeMove = confirmWorkspaceMove
1044
+ /** @deprecated renamed to `confirmWorkspaceDeleteModal`. */
1045
+ export const confirmWorktreeDeleteModal = confirmWorkspaceDeleteModal