@brimveyn/aimux-config 0.8.6 → 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' }],
@@ -59,7 +60,7 @@ export function toggleWidget(widgetId: string): KeyResult {
59
60
  return r([{ type: 'toggle-widget', widgetId }])
60
61
  }
61
62
  export const toggleGitPane: KeyResult = toggleWidget('git')
62
- export const toggleSessionBar: KeyResult = r([{ type: 'toggle-session-bar' }])
63
+ export const toggleProjectBar: KeyResult = r([{ type: 'toggle-project-bar' }])
63
64
  export const toggleAIUsage: ActionFn = (ctx: ModeContext) => {
64
65
  if (ctx.state.modal.type === 'ai-usage') {
65
66
  return r([{ type: 'close-modal' }], [], 'navigation')
@@ -69,8 +70,8 @@ export const toggleAIUsage: ActionFn = (ctx: ModeContext) => {
69
70
 
70
71
  export const enterGitMode: KeyResult = r([{ type: 'enter-git-mode' }], [], 'git-mode')
71
72
 
72
- export function switchSessionByIndex(index: number): KeyResult {
73
- return r([], [{ index, type: 'switch-session-by-index' }])
73
+ export function switchProjectByIndex(index: number): KeyResult {
74
+ return r([], [{ index, type: 'switch-project-by-index' }])
74
75
  }
75
76
 
76
77
  export function switchTabByIndex(index: number): KeyResult {
@@ -134,8 +135,8 @@ export function reorderTab(delta: number): KeyResult {
134
135
  return r([{ delta, type: 'reorder-active-tab' }])
135
136
  }
136
137
 
137
- export function reorderSession(delta: number): KeyResult {
138
- return r([{ delta, type: 'reorder-active-session' }])
138
+ export function reorderProject(delta: number): KeyResult {
139
+ return r([{ delta, type: 'reorder-active-project' }])
139
140
  }
140
141
 
141
142
  export function resizeBar(side: 'left' | 'right', delta: number): KeyResult {
@@ -199,70 +200,20 @@ export function moveModalSelectionWithPreview(
199
200
  // Modal-specific actions
200
201
  // ---------------------------------------------------------------------------
201
202
 
202
- export const launchSelectedAssistant: ActionFn = (ctx: ModeContext) => {
203
- if (ctx.state.modal.type === 'new-tab' && ctx.state.modal.step === 'assistant') {
204
- return r([{ type: 'select-new-tab-assistant' }])
205
- }
206
- if (
207
- ctx.state.modal.type === 'new-tab' &&
208
- ctx.state.modal.step === 'worktree' &&
209
- ctx.state.modal.createWorktree
210
- ) {
211
- return r([{ type: 'enter-new-tab-worktree-create' }], [{ type: 'load-new-tab-base-branches' }])
212
- }
213
- return r([], [{ type: 'launch-selected-assistant' }])
214
- }
215
-
216
- export const toggleNewTabWorktree: KeyResult = r([{ type: 'toggle-new-tab-worktree' }])
203
+ export const launchSelectedAssistant: KeyResult = r([], [{ type: 'launch-selected-assistant' }])
217
204
 
218
- export const deleteSelectedWorktree: ActionFn = (ctx: ModeContext) => {
205
+ export const confirmWorkspaceDeleteModal: ActionFn = (ctx: ModeContext) => {
219
206
  const modal = ctx.state.modal
220
- const sessionId = ctx.state.currentSessionId
221
- if (modal.type !== 'new-tab' || modal.step !== 'worktree' || modal.createWorktree) return null
222
- if (!(sessionId != null && sessionId !== '')) return null
223
- const session = ctx.state.sessions.find((entry) => entry.id === sessionId)
224
- const worktree = session?.worktrees?.[modal.selectedIndex]
225
- if (!worktree) return null
226
- // First attempt is never forced — a recoverable failure opens the confirm
227
- // dialog (see set-new-tab-worktree-delete-prompt), where confirmDeleteWorktree
228
- // retries with force.
229
- return r(
230
- [{ index: modal.selectedIndex, type: 'set-modal-selection-index' }],
231
- [{ force: false, sessionId, type: 'delete-worktree', worktreeId: worktree.id }]
232
- )
233
- }
234
-
235
- export const confirmDeleteWorktree: ActionFn = (ctx: ModeContext) => {
236
- const modal = ctx.state.modal
237
- const sessionId = ctx.state.currentSessionId
238
- if (modal.type !== 'new-tab' || modal.worktreeDeletePrompt == null) return null
239
- if (!(sessionId != null && sessionId !== '')) return null
240
- const { worktreeId } = modal.worktreeDeletePrompt
241
- return r(
242
- [{ prompt: null, type: 'set-new-tab-worktree-delete-prompt' }],
243
- [{ force: true, sessionId, type: 'delete-worktree', worktreeId }],
244
- 'modal.new-tab.command-edit'
245
- )
246
- }
247
-
248
- export const cancelDeleteWorktree: KeyResult = r(
249
- [{ prompt: null, type: 'set-new-tab-worktree-delete-prompt' }],
250
- [],
251
- 'modal.new-tab.command-edit'
252
- )
253
-
254
- export const confirmWorktreeDeleteModal: ActionFn = (ctx: ModeContext) => {
255
- const modal = ctx.state.modal
256
- if (modal.type !== 'worktree-delete-confirm') return null
207
+ if (modal.type !== 'workspace-delete-confirm') return null
257
208
  return r(
258
209
  [{ type: 'close-modal' }],
259
210
  [
260
211
  {
261
212
  closeTabs: modal.closeTabs,
262
213
  force: modal.force,
263
- sessionId: modal.sessionId,
264
- type: 'delete-worktree',
265
- worktreeId: modal.worktreeId,
214
+ projectId: modal.projectId,
215
+ type: 'delete-workspace',
216
+ workspaceId: modal.workspaceId,
266
217
  },
267
218
  ],
268
219
  'navigation'
@@ -272,20 +223,52 @@ export const confirmWorktreeDeleteModal: ActionFn = (ctx: ModeContext) => {
272
223
  export const cancelCommandEdit = (returnTo: KeyResult['transition']): KeyResult =>
273
224
  r([{ type: 'cancel-command-edit' }], [], returnTo)
274
225
 
275
- export const confirmSelectedSession: KeyResult = r([], [{ type: 'confirm-selected-session' }])
226
+ export const confirmSelectedProject: KeyResult = r([], [{ type: 'confirm-selected-project' }])
276
227
 
277
- export const openCreateSessionModal: KeyResult = r(
278
- [{ returnToSessionPicker: true, type: 'open-create-session-modal' }],
228
+ export const openCreateProjectModal: KeyResult = r(
229
+ [{ returnToProjectPicker: true, type: 'open-create-project-modal' }],
279
230
  [],
280
- 'modal.create-session'
231
+ 'modal.create-project'
232
+ )
233
+
234
+ export const createWorkspaceModal: KeyResult = r(
235
+ [{ type: 'open-create-workspace-modal' }],
236
+ [{ type: 'load-create-workspace-base-branches' }],
237
+ 'modal.create-workspace'
281
238
  )
282
239
 
283
- export const openRenameSelectedSession: KeyResult = r(
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(
284
267
  [],
285
- [{ type: 'open-rename-selected-session' }]
268
+ [{ type: 'open-rename-selected-project' }]
286
269
  )
287
270
 
288
- export const deleteSelectedSession: KeyResult = r([], [{ type: 'delete-selected-session' }])
271
+ export const deleteSelectedProject: KeyResult = r([], [{ type: 'delete-selected-project' }])
289
272
 
290
273
  export const openSnippetEditor: KeyResult = r(
291
274
  [{ type: 'open-snippet-editor' }],
@@ -336,20 +319,20 @@ export function previewTheme(delta: 1 | -1): KeyResult {
336
319
  export const toggleTransparent: KeyResult = r([], [{ type: 'toggle-transparent' }])
337
320
  export const toggleMode: KeyResult = r([], [{ type: 'toggle-mode' }])
338
321
 
339
- export const switchField: KeyResult = r([{ type: 'switch-create-session-field' }])
322
+ export const switchField: KeyResult = r([{ type: 'switch-create-project-field' }])
340
323
  export const selectDirectory: KeyResult = r([{ type: 'select-directory' }])
341
324
 
342
- export const backToSessionPicker: KeyResult = r(
343
- [{ type: 'open-session-picker' }],
325
+ export const backToProjectPicker: KeyResult = r(
326
+ [{ type: 'open-project-picker' }],
344
327
  [],
345
- 'modal.session-picker.filtering'
328
+ 'modal.project-picker.filtering'
346
329
  )
347
330
 
348
- export const createSessionEscape: ActionFn = (ctx: ModeContext) => {
349
- 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) {
350
333
  return r([{ type: 'close-modal' }], [], 'navigation')
351
334
  }
352
- return r([{ type: 'open-session-picker' }], [], 'modal.session-picker.filtering')
335
+ return r([{ type: 'open-project-picker' }], [], 'modal.project-picker.filtering')
353
336
  }
354
337
 
355
338
  export const backToSnippetPicker: KeyResult = r(
@@ -393,10 +376,10 @@ export const confirmUpdateSelection: KeyResult = r(
393
376
  )
394
377
 
395
378
  // ---------------------------------------------------------------------------
396
- // Worktree-move modal
379
+ // Workspace-move modal
397
380
  // ---------------------------------------------------------------------------
398
381
 
399
- export const openWorktreeMove: ActionFn = (ctx: ModeContext) => {
382
+ export const openWorkspaceMove: ActionFn = (ctx: ModeContext) => {
400
383
  if (ctx.state.gitMode.headOffset > 0) {
401
384
  return r([
402
385
  {
@@ -405,35 +388,35 @@ export const openWorktreeMove: ActionFn = (ctx: ModeContext) => {
405
388
  },
406
389
  ])
407
390
  }
408
- const session = ctx.state.sessions.find((entry) => entry.id === ctx.state.currentSessionId)
409
- const worktrees = session?.worktrees ?? []
410
- // From git mode, the source is the active worktree (the one being reviewed).
411
- 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]
412
395
  const hasBranch = source != null && source.branch != null && source.branch !== ''
413
- const others = worktrees.filter((w) => w.id !== source?.id)
396
+ const others = workspaces.filter((w) => w.id !== source?.id)
414
397
  if (!hasBranch || source == null || others.length < 1) {
415
- 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' }])
416
399
  }
417
400
  // Overlay: no mode transition — deriveModeId routes input to the picker and
418
401
  // focusMode stays 'git' so the git view remains mounted underneath.
419
402
  return r(
420
- [{ sourceWorktreeId: source.id, type: 'open-worktree-move-modal' }],
421
- [{ type: 'load-worktree-move-stats' }]
403
+ [{ sourceWorkspaceId: source.id, type: 'open-workspace-move-modal' }],
404
+ [{ type: 'load-workspace-move-stats' }]
422
405
  )
423
406
  }
424
407
 
425
- export const toggleWorktreeMoveDelete: KeyResult = r([{ type: 'toggle-worktree-move-delete' }])
408
+ export const toggleWorkspaceMoveDelete: KeyResult = r([{ type: 'toggle-workspace-move-delete' }])
426
409
 
427
- export const confirmWorktreeMove: ActionFn = (ctx: ModeContext) => {
410
+ export const confirmWorkspaceMove: ActionFn = (ctx: ModeContext) => {
428
411
  const modal = ctx.state.modal
429
- const session = ctx.state.sessions.find((entry) => entry.id === ctx.state.currentSessionId)
430
- const worktrees = session?.worktrees ?? []
431
- const sourceId = modal.type === 'worktree-move' ? modal.sourceWorktreeId : undefined
432
- const source = worktrees.find((w) => w.id === sourceId)
433
- const targets = worktrees.filter((w) => w.id !== sourceId)
434
- 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
435
418
  const target = targets[selectedIndex]
436
- if (!target || !session || !source || modal.type !== 'worktree-move') {
419
+ if (!target || !project || !source || modal.type !== 'workspace-move') {
437
420
  return r([{ type: 'close-modal' }])
438
421
  }
439
422
  // Overlay close keeps focusMode 'git' (see close-modal reducer), so the move
@@ -443,10 +426,10 @@ export const confirmWorktreeMove: ActionFn = (ctx: ModeContext) => {
443
426
  [
444
427
  {
445
428
  deleteSource: modal.deleteSource,
446
- sessionId: session.id,
447
- sourceWorktreeId: source.id,
448
- targetWorktreeId: target.id,
449
- type: 'move-worktree',
429
+ projectId: project.id,
430
+ sourceWorkspaceId: source.id,
431
+ targetWorkspaceId: target.id,
432
+ type: 'move-workspace',
450
433
  },
451
434
  ]
452
435
  )
@@ -454,18 +437,18 @@ export const confirmWorktreeMove: ActionFn = (ctx: ModeContext) => {
454
437
 
455
438
  // Confirms the dialog opened after a recoverable move failure: re-runs the
456
439
  // move with the flag matching the failure (stash the target / keep conflicts).
457
- export const confirmWorktreeMoveRetry: ActionFn = (ctx: ModeContext) => {
440
+ export const confirmWorkspaceMoveRetry: ActionFn = (ctx: ModeContext) => {
458
441
  const modal = ctx.state.modal
459
- if (modal.type !== 'worktree-move-confirm') return null
442
+ if (modal.type !== 'workspace-move-confirm') return null
460
443
  return r(
461
444
  [{ type: 'close-modal' }],
462
445
  [
463
446
  {
464
447
  deleteSource: modal.deleteSource,
465
- sessionId: modal.sessionId,
466
- sourceWorktreeId: modal.sourceWorktreeId,
467
- targetWorktreeId: modal.targetWorktreeId,
468
- type: 'move-worktree',
448
+ projectId: modal.projectId,
449
+ sourceWorkspaceId: modal.sourceWorkspaceId,
450
+ targetWorkspaceId: modal.targetWorkspaceId,
451
+ type: 'move-workspace',
469
452
  ...(modal.variant === 'stash-target' ? { stashTarget: true } : { keepConflicts: true }),
470
453
  },
471
454
  ],
@@ -487,9 +470,9 @@ export const closePane: ActionFn = (ctx: ModeContext) => {
487
470
  }
488
471
 
489
472
  // Navigation-specific dynamic actions
490
- // Reveals whichever bar hosts the workspace list — it is not pinned to the left.
473
+ // Reveals whichever bar hosts the project list — it is not pinned to the left.
491
474
  export const ctrlZSidebar: ActionFn = (ctx: ModeContext) => {
492
- const side = ctx.state.bars.left.widgets.some((w) => w.id === 'workspaces') ? 'left' : 'right'
475
+ const side = ctx.state.bars.left.widgets.some((w) => w.id === 'projects') ? 'left' : 'right'
493
476
  if (!ctx.state.bars[side].visible) {
494
477
  return r([{ side, type: 'toggle-bar' }])
495
478
  }
@@ -509,21 +492,21 @@ export const leaveTerminalInput: KeyResult = r(
509
492
 
510
493
  export const toggleSidebarFromInput: KeyResult = toggleBar('left')
511
494
 
512
- // Session name modal
513
- export const confirmSessionRename: ActionFn = (ctx: ModeContext) => {
495
+ // Project name modal
496
+ export const confirmProjectRename: ActionFn = (ctx: ModeContext) => {
514
497
  const trimmed = (ctx.state.modal.editBuffer ?? '').trim()
515
- const sessionId = ctx.state.modal.sessionTargetId
498
+ const projectId = ctx.state.modal.projectTargetId
516
499
  const returnToPicker =
517
- ctx.state.modal.type === 'session-name' ? ctx.state.modal.returnToSessionPicker : true
500
+ ctx.state.modal.type === 'project-name' ? ctx.state.modal.returnToProjectPicker : true
518
501
  const closeAction: AppAction = returnToPicker
519
- ? { type: 'open-session-picker' }
502
+ ? { type: 'open-project-picker' }
520
503
  : { type: 'close-modal' }
521
504
  const transition: KeyResult['transition'] = returnToPicker
522
- ? 'modal.session-picker.filtering'
505
+ ? 'modal.project-picker.filtering'
523
506
  : 'navigation'
524
507
  const effects: KeyResult['effects'] =
525
- trimmed && sessionId != null && sessionId !== ''
526
- ? [{ name: trimmed, sessionId, type: 'rename-session' }]
508
+ trimmed && projectId != null && projectId !== ''
509
+ ? [{ name: trimmed, projectId, type: 'rename-project' }]
527
510
  : []
528
511
  return r([closeAction], effects, transition)
529
512
  }
@@ -531,7 +514,7 @@ export const confirmSessionRename: ActionFn = (ctx: ModeContext) => {
531
514
  // Rename tab modal
532
515
  export const confirmRenameTab: ActionFn = (ctx: ModeContext) => {
533
516
  const trimmed = (ctx.state.modal.editBuffer ?? '').trim()
534
- const tabId = ctx.state.modal.sessionTargetId
517
+ const tabId = ctx.state.modal.projectTargetId
535
518
  const actions: KeyResult['actions'] = []
536
519
  const effects: KeyResult['effects'] = []
537
520
  if (trimmed && tabId != null && tabId !== '') {
@@ -541,27 +524,33 @@ export const confirmRenameTab: ActionFn = (ctx: ModeContext) => {
541
524
  return r(actions, effects, 'navigation')
542
525
  }
543
526
 
544
- // Rename worktree modal
545
- export const confirmRenameWorktree: ActionFn = (ctx: ModeContext) => {
527
+ // Rename workspace modal
528
+ export const confirmRenameWorkspace: ActionFn = (ctx: ModeContext) => {
546
529
  const { modal } = ctx.state
547
530
  const trimmed = (modal.editBuffer ?? '').trim()
548
- const worktreeId = modal.sessionTargetId
549
- const sessionId = modal.type === 'rename-worktree' ? modal.worktreeSessionId : null
531
+ const workspaceId = modal.projectTargetId
532
+ const projectId = modal.type === 'rename-workspace' ? modal.workspaceProjectId : null
550
533
  const actions: KeyResult['actions'] = []
551
- if (trimmed && worktreeId != null && worktreeId !== '' && sessionId != null && sessionId !== '') {
534
+ if (
535
+ trimmed &&
536
+ workspaceId != null &&
537
+ workspaceId !== '' &&
538
+ projectId != null &&
539
+ projectId !== ''
540
+ ) {
552
541
  actions.push({
553
542
  patch: { name: trimmed },
554
- sessionId,
555
- type: 'update-worktree-record',
556
- worktreeId,
543
+ projectId,
544
+ type: 'update-workspace-record',
545
+ workspaceId,
557
546
  })
558
547
  }
559
548
  actions.push({ type: 'close-modal' })
560
549
  return r(actions, [], 'navigation')
561
550
  }
562
551
 
563
- // Create session modal
564
- export const confirmCreateSession: ActionFn = (ctx: ModeContext) => {
552
+ // Create project modal
553
+ export const confirmCreateProject: ActionFn = (ctx: ModeContext) => {
565
554
  const modal = ctx.state.modal as {
566
555
  activeField?: string
567
556
  editBuffer?: string
@@ -574,26 +563,26 @@ export const confirmCreateSession: ActionFn = (ctx: ModeContext) => {
574
563
 
575
564
  const trimmed = (modal.editBuffer ?? '').trim()
576
565
  const projectPath = modal.pendingProjectPath ?? undefined
577
- const sessionName = trimmed || getDefaultSessionName(projectPath)
578
- if (sessionName) {
566
+ const projectName = trimmed || getDefaultProjectName(projectPath)
567
+ if (projectName) {
579
568
  return r(
580
569
  [{ type: 'close-modal' }],
581
- [{ name: sessionName, projectPath, type: 'create-session' }],
570
+ [{ name: projectName, projectPath, type: 'create-project' }],
582
571
  'navigation'
583
572
  )
584
573
  }
585
574
  return r([{ type: 'close-modal' }], [], 'navigation')
586
575
  }
587
576
 
588
- function getDefaultSessionName(projectPath?: string): string {
577
+ function getDefaultProjectName(projectPath?: string): string {
589
578
  if (!(projectPath != null && projectPath !== '')) return ''
590
579
  const segments = projectPath.split('/').filter(Boolean)
591
580
  return segments.at(-1) ?? ''
592
581
  }
593
582
 
594
- // Session picker escape (conditional)
595
- export const sessionPickerEscape: ActionFn = (ctx: ModeContext) => {
596
- 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
597
586
  return r([{ type: 'close-modal' }], [], 'navigation')
598
587
  }
599
588
 
@@ -833,9 +822,9 @@ export const gitCommitOpen: ActionFn = (ctx: ModeContext) => {
833
822
  },
834
823
  ])
835
824
  }
836
- const sessionId = ctx.state.currentSessionId ?? undefined
825
+ const projectId = ctx.state.currentProjectId ?? undefined
837
826
  return r(
838
- [...clearPendingDelete(ctx), { sessionId, type: 'open-git-commit-modal' }],
827
+ [...clearPendingDelete(ctx), { projectId, type: 'open-git-commit-modal' }],
839
828
  [],
840
829
  'modal.git-commit'
841
830
  )
@@ -894,13 +883,13 @@ export const gitCommitEnterConfirm: ActionFn = (ctx: ModeContext) => {
894
883
  if (hasTitle) {
895
884
  return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
896
885
  }
897
- const sessionId = ctx.state.currentSessionId
898
- if (!(sessionId != null && sessionId !== '')) {
886
+ const projectId = ctx.state.currentProjectId
887
+ if (!(projectId != null && projectId !== '')) {
899
888
  return r([{ type: 'git-commit-enter-confirm' }], [], 'modal.git-commit.confirm')
900
889
  }
901
890
  return r(
902
- [{ sessionId, type: 'git-commit-enter-generating' }],
903
- [{ sessionId, type: 'generate-auto-commit-now' }],
891
+ [{ projectId, type: 'git-commit-enter-generating' }],
892
+ [{ projectId, type: 'generate-auto-commit-now' }],
904
893
  'modal.git-commit.generating'
905
894
  )
906
895
  }
@@ -912,10 +901,10 @@ export const gitCommitLeaveConfirm: KeyResult = r(
912
901
  )
913
902
 
914
903
  export const gitCommitLeaveGenerating: ActionFn = (ctx: ModeContext) => {
915
- const sessionId = ctx.state.currentSessionId
904
+ const projectId = ctx.state.currentProjectId
916
905
  const actionsList: AppAction[] = [{ type: 'git-commit-leave-generating' }]
917
- if (sessionId != null && sessionId !== '') {
918
- actionsList.push({ sessionId, type: 'auto-commit-clear' })
906
+ if (projectId != null && projectId !== '') {
907
+ actionsList.push({ projectId, type: 'auto-commit-clear' })
919
908
  }
920
909
  return r(actionsList, [], 'modal.git-commit')
921
910
  }
@@ -952,5 +941,105 @@ export const gitCommitReturnKey: ActionFn = (ctx: ModeContext) => {
952
941
  if (modal.type === 'git-commit' && modal.activeField === 'body') {
953
942
  return r([{ char: '\n', type: 'update-command-edit' }])
954
943
  }
955
- 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' }])
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
+ )
956
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