@brimveyn/aimux-config 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,7 +77,13 @@ defineConfig({
77
77
  ```ts
78
78
  k.leader(keys) // default: '<Space>'
79
79
  .timeout(ms) // default: 300
80
- .mode(id, configure) // define bindings for a mode
80
+ .mode(id | ids[], configure) // define bindings for a mode (or several at once)
81
+ ```
82
+
83
+ Pass an array of `ModeId`s to register the same bindings in every listed mode — handy for actions that should fire in both `navigation` and `terminal-input`, for example:
84
+
85
+ ```ts
86
+ k.mode(['navigation', 'terminal-input'], (m) => m.map('<C-s>', actions.snippetPicker))
81
87
  ```
82
88
 
83
89
  ### Mode builder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux-config",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript configuration API for aimux — keymaps, themes, and backends with a fluent builder.",
5
5
  "keywords": [
6
6
  "aimux",
package/src/actions.ts CHANGED
@@ -36,8 +36,13 @@ export const helpModal: KeyResult = r([{ type: 'open-help-modal' }], [], 'modal.
36
36
 
37
37
  export const toggleSidebar: KeyResult = r([{ type: 'toggle-sidebar' }])
38
38
  export const toggleGitPanel: KeyResult = r([{ type: 'toggle-git-panel' }])
39
+ export const toggleSessionBar: KeyResult = r([{ type: 'toggle-session-bar' }])
39
40
  export const enterGitMode: KeyResult = r([{ type: 'enter-git-mode' }], [], 'git-mode')
40
41
 
42
+ export function switchSessionByIndex(index: number): KeyResult {
43
+ return r([], [{ index, type: 'switch-session-by-index' }])
44
+ }
45
+
41
46
  export const splitVertical: KeyResult = r(
42
47
  [{ direction: 'vertical', type: 'open-split-picker' }],
43
48
  [],
@@ -237,6 +242,16 @@ export const saveSnippetEditor: KeyResult = r(
237
242
  'navigation'
238
243
  )
239
244
 
245
+ // ---------------------------------------------------------------------------
246
+ // Update-available modal
247
+ // ---------------------------------------------------------------------------
248
+
249
+ export const confirmUpdateSelection: KeyResult = r(
250
+ [{ type: 'close-modal' }],
251
+ [{ type: 'confirm-update-selection' }],
252
+ 'navigation'
253
+ )
254
+
240
255
  // Layout-specific
241
256
  export const exitLayoutToInput: KeyResult = r(
242
257
  [{ focusMode: 'terminal-input', type: 'set-focus-mode' }],
package/src/defaults.ts CHANGED
@@ -12,262 +12,290 @@ export function getDefaultKeymapConfig(): ResolvedKeymapConfig {
12
12
  .timeout(300)
13
13
 
14
14
  // -----------------------------------------------------------------------
15
- // Navigation mode (src/input/modes/handlers/navigation.ts)
15
+ // Navigation mode
16
16
  // -----------------------------------------------------------------------
17
17
  .mode('navigation', (m) =>
18
18
  m
19
- .map('<C-c>', actions.quit)
20
- .map('<C-n>', actions.newTab)
21
- .map('<C-g>', actions.sessionPicker)
22
- .map('<C-z>', actions.ctrlZSidebar)
23
- .map('dd', actions.closeTab)
24
- .map('<C-b>', actions.toggleSidebar)
25
- .map('<C-r>', actions.restartTab)
26
- .map('<C-s>', actions.snippetPicker)
27
- .map('<C-t>', actions.themePicker)
28
- .map('<C-h>', actions.resizeSidebar(-2))
29
- .map('<C-l>', actions.resizeSidebar(2))
30
- .map('G', actions.toggleGitPanel)
31
- .map('<C-d>', actions.enterGitMode)
32
- .map('<C-j>', actions.resizeGitPanel(-0.05))
33
- .map('<C-k>', actions.resizeGitPanel(0.05))
34
- .map('J', actions.reorderTab(1))
35
- .map('j', actions.nextTab)
36
- .map('K', actions.reorderTab(-1))
37
- .map('k', actions.prevTab)
38
- .map('r', actions.renameTab)
39
- .map('i', actions.enterInsert)
40
- .map('?', actions.helpModal)
19
+ .map('<C-c>', actions.quit, 'Quit')
20
+ .map('<C-n>', actions.newTab, 'New tab')
21
+ .map('<C-g>', actions.sessionPicker, 'Session picker')
22
+ .map('<C-z>', actions.ctrlZSidebar, 'Focus sidebar')
23
+ .map('dd', actions.closeTab, 'Close tab')
24
+ .map('<C-b>', actions.toggleSidebar, 'Toggle sidebar')
25
+ .map('<C-r>', actions.restartTab, 'Restart tab')
26
+ .map('<C-s>', actions.snippetPicker, 'Snippet picker')
27
+ .map('<C-t>', actions.themePicker, 'Theme picker')
28
+ .map('<C-h>', actions.resizeSidebar(-2), 'Sidebar narrower')
29
+ .map('<C-l>', actions.resizeSidebar(2), 'Sidebar wider')
30
+ .map('G', actions.toggleGitPanel, 'Toggle git panel')
31
+ .map('<C-d>', actions.enterGitMode, 'Enter git mode')
32
+ .map('<C-j>', actions.resizeGitPanel(-0.05), 'Git panel smaller')
33
+ .map('<C-k>', actions.resizeGitPanel(0.05), 'Git panel larger')
34
+ .map('J', actions.reorderTab(1), 'Move tab right')
35
+ .map('j', actions.nextTab, 'Next tab')
36
+ .map('K', actions.reorderTab(-1), 'Move tab left')
37
+ .map('k', actions.prevTab, 'Prev tab')
38
+ .map('r', actions.renameTab, 'Rename tab')
39
+ .map('i', actions.enterInsert, 'Focus terminal')
40
+ .map('?', actions.helpModal, 'Help')
41
41
  )
42
42
 
43
43
  // -----------------------------------------------------------------------
44
44
  // Terminal-input mode
45
- // Bindings here are processed by raw-input-handler.ts before bytes reach
46
- // the PTY. Only single-chord <C-*> sequences are supported (no multi-key).
47
45
  // -----------------------------------------------------------------------
48
46
  .mode('terminal-input', (m) =>
49
47
  m
50
- .map('<C-z>', actions.leaveTerminalInput)
51
- .map('<Leader>', actions.enterLayoutMode)
52
- .map('<C-b>', actions.toggleSidebarFromInput)
48
+ .map('<C-z>', actions.leaveTerminalInput, 'Leave insert')
49
+ .map('<Leader>', actions.enterLayoutMode, 'Layout mode')
50
+ .map('<C-b>', actions.toggleSidebarFromInput, 'Toggle sidebar')
53
51
  )
54
52
 
55
53
  // -----------------------------------------------------------------------
56
- // Layout mode (src/input/modes/handlers/layout.ts)
54
+ // Session bar: same chords from nav and while typing in the terminal
55
+ // -----------------------------------------------------------------------
56
+ .mode(['navigation', 'terminal-input'], (m) =>
57
+ m
58
+ .map('<Leader>b', actions.toggleSessionBar, 'Toggle session bar')
59
+ .map('<Leader>1', actions.switchSessionByIndex(1), 'Session 1')
60
+ .map('<Leader>2', actions.switchSessionByIndex(2), 'Session 2')
61
+ .map('<Leader>3', actions.switchSessionByIndex(3), 'Session 3')
62
+ .map('<Leader>4', actions.switchSessionByIndex(4), 'Session 4')
63
+ .map('<Leader>5', actions.switchSessionByIndex(5), 'Session 5')
64
+ .map('<Leader>6', actions.switchSessionByIndex(6), 'Session 6')
65
+ .map('<Leader>7', actions.switchSessionByIndex(7), 'Session 7')
66
+ .map('<Leader>8', actions.switchSessionByIndex(8), 'Session 8')
67
+ .map('<Leader>9', actions.switchSessionByIndex(9), 'Session 9')
68
+ )
69
+
70
+ // -----------------------------------------------------------------------
71
+ // Layout mode
57
72
  // -----------------------------------------------------------------------
58
73
  .mode('layout', (m) =>
59
74
  m
60
- .map('<Esc>', actions.exitLayoutToInput)
61
- .map('<Leader>', actions.exitLayoutToInput)
62
- .map('<C-z>', actions.exitLayoutToNavigation)
63
- .map('H', actions.resizePane(-1, 'vertical'))
64
- .map('L', actions.resizePane(1, 'vertical'))
65
- .map('K', actions.resizePane(-1, 'horizontal'))
66
- .map('J', actions.resizePane(1, 'horizontal'))
67
- .map('h', actions.focusPane('left'))
68
- .map('j', actions.focusPane('down'))
69
- .map('k', actions.focusPane('up'))
70
- .map('l', actions.focusPane('right'))
71
- .map('|', actions.splitVertical)
72
- .map('-', actions.splitHorizontal)
73
- .map('q', actions.closePane)
75
+ .map('<Esc>', actions.exitLayoutToInput, 'Back to insert')
76
+ .map('<Leader>', actions.exitLayoutToInput, 'Back to insert')
77
+ .map('<C-z>', actions.exitLayoutToNavigation, 'Back to nav')
78
+ .map('H', actions.resizePane(-1, 'vertical'), 'Shrink ←')
79
+ .map('L', actions.resizePane(1, 'vertical'), 'Grow →')
80
+ .map('K', actions.resizePane(-1, 'horizontal'), 'Shrink ↑')
81
+ .map('J', actions.resizePane(1, 'horizontal'), 'Grow ↓')
82
+ .map('h', actions.focusPane('left'), 'Focus left')
83
+ .map('j', actions.focusPane('down'), 'Focus down')
84
+ .map('k', actions.focusPane('up'), 'Focus up')
85
+ .map('l', actions.focusPane('right'), 'Focus right')
86
+ .map('|', actions.splitVertical, 'Split vertical')
87
+ .map('-', actions.splitHorizontal, 'Split horizontal')
88
+ .map('q', actions.closePane, 'Close pane')
74
89
  )
75
90
 
76
91
  // -----------------------------------------------------------------------
77
- // Git mode (src/input/modes/handlers/git-mode.ts)
92
+ // Git mode
78
93
  // -----------------------------------------------------------------------
79
94
  .mode('git-mode', (m) =>
80
95
  m
81
- .map('<Esc>', actions.exitGitMode)
82
- .map('j', actions.selectGitFile(1))
83
- .map('k', actions.selectGitFile(-1))
84
- .map('<C-d>', actions.scrollGitDiff(20))
85
- .map('<C-u>', actions.scrollGitDiff(-20))
86
- .map('<Down>', actions.scrollGitDiff(1))
87
- .map('<Up>', actions.scrollGitDiff(-1))
88
- .map('a', actions.gitStageSelected)
89
- .map('d', actions.gitDestructiveSelected)
90
- .map('c', actions.gitCommitOpen)
91
- .map('p', actions.gitPush)
96
+ .map('<Esc>', actions.exitGitMode, 'Exit git')
97
+ .map('j', actions.selectGitFile(1), 'Next file')
98
+ .map('k', actions.selectGitFile(-1), 'Prev file')
99
+ .map('<C-d>', actions.scrollGitDiff(20), 'Page down')
100
+ .map('<C-u>', actions.scrollGitDiff(-20), 'Page up')
101
+ .map('<Down>', actions.scrollGitDiff(1), 'Scroll down')
102
+ .map('<Up>', actions.scrollGitDiff(-1), 'Scroll up')
103
+ .map('a', actions.gitStageSelected, 'Stage')
104
+ .map('d', actions.gitDestructiveSelected, 'Unstage/delete')
105
+ .map('c', actions.gitCommitOpen, 'Commit')
106
+ .map('p', actions.gitPush, 'Push')
92
107
  )
93
108
 
94
109
  // -----------------------------------------------------------------------
95
- // Modal: help (src/input/modes/handlers/modal-help.ts)
110
+ // Modal: help
96
111
  // -----------------------------------------------------------------------
97
- .mode('modal.help', (m) => m.map('<Esc>', actions.closeModal))
112
+ .mode('modal.help', (m) => m.map('<Esc>', actions.closeModal, 'Close'))
98
113
 
99
114
  // -----------------------------------------------------------------------
100
- // Modal: theme-picker (src/input/modes/handlers/modal-theme-picker.ts)
115
+ // Modal: theme-picker
101
116
  // -----------------------------------------------------------------------
102
117
  .mode('modal.theme-picker', (m) =>
103
118
  m
104
- .map('<Esc>', actions.restoreTheme)
105
- .map('j', actions.previewTheme(1))
106
- .map('k', actions.previewTheme(-1))
119
+ .map('<Esc>', actions.restoreTheme, 'Cancel')
120
+ .map('j', actions.previewTheme(1), 'Next')
121
+ .map('k', actions.previewTheme(-1), 'Prev')
107
122
  .map('<Down>', actions.previewTheme(1))
108
123
  .map('<Up>', actions.previewTheme(-1))
109
- .map('<CR>', actions.confirmTheme)
124
+ .map('<CR>', actions.confirmTheme, 'Confirm')
125
+ )
126
+
127
+ // -----------------------------------------------------------------------
128
+ // Modal: update-available
129
+ // -----------------------------------------------------------------------
130
+ .mode('modal.update-available', (m) =>
131
+ m
132
+ .map('<Esc>', actions.closeModal, 'Cancel')
133
+ .map('l', actions.moveModalSelection(1), 'Next')
134
+ .map('h', actions.moveModalSelection(-1), 'Prev')
135
+ .map('j', actions.moveModalSelection(1))
136
+ .map('k', actions.moveModalSelection(-1))
137
+ .map('<Right>', actions.moveModalSelection(1))
138
+ .map('<Left>', actions.moveModalSelection(-1))
139
+ .map('<Down>', actions.moveModalSelection(1))
140
+ .map('<Up>', actions.moveModalSelection(-1))
141
+ .map('<CR>', actions.confirmUpdateSelection, 'Confirm')
110
142
  )
111
143
 
112
144
  // -----------------------------------------------------------------------
113
- // Modal: rename-tab (src/input/modes/handlers/modal-rename-tab.ts)
145
+ // Modal: rename-tab
114
146
  // -----------------------------------------------------------------------
115
147
  .mode('modal.rename-tab', (m) =>
116
- m.map('<Esc>', actions.closeModal).map('<CR>', actions.confirmRenameTab).passthrough()
148
+ m
149
+ .map('<Esc>', actions.closeModal, 'Cancel')
150
+ .map('<CR>', actions.confirmRenameTab, 'Confirm')
151
+ .passthrough()
117
152
  )
118
153
 
119
154
  // -----------------------------------------------------------------------
120
- // Modal: new-tab (src/input/modes/handlers/modal-new-tab.ts)
155
+ // Modal: new-tab
121
156
  // -----------------------------------------------------------------------
122
157
  .mode('modal.new-tab', (m) =>
123
158
  m
124
- .map('<Esc>', actions.closeModal)
125
- .map('j', actions.moveModalSelection(1))
126
- .map('k', actions.moveModalSelection(-1))
159
+ .map('<Esc>', actions.closeModal, 'Cancel')
160
+ .map('j', actions.moveModalSelection(1), 'Next')
161
+ .map('k', actions.moveModalSelection(-1), 'Prev')
127
162
  .map('<Down>', actions.moveModalSelection(1))
128
163
  .map('<Up>', actions.moveModalSelection(-1))
129
- .map('<CR>', actions.launchSelectedAssistant)
130
- .map('e', actions.beginCommandEdit)
164
+ .map('<CR>', actions.launchSelectedAssistant, 'Launch')
165
+ .map('e', actions.beginCommandEdit, 'Edit command')
131
166
  )
132
167
 
133
168
  // -----------------------------------------------------------------------
134
169
  // Modal: new-tab command-edit
135
- // (src/input/modes/handlers/modal-new-tab-command-edit.ts)
136
170
  // -----------------------------------------------------------------------
137
171
  .mode('modal.new-tab.command-edit', (m) =>
138
172
  m
139
- .map('<Esc>', actions.cancelCommandEdit('modal.new-tab'))
140
- .map('<CR>', actions.commitCommandEdit)
141
- .map('<C-n>', actions.moveModalSelection(1))
142
- .map('<C-p>', actions.moveModalSelection(-1))
173
+ .map('<Esc>', actions.cancelCommandEdit('modal.new-tab'), 'Cancel')
174
+ .map('<CR>', actions.commitCommandEdit, 'Save')
175
+ .map('<C-n>', actions.moveModalSelection(1), 'Next')
176
+ .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
143
177
  .passthrough()
144
178
  )
145
179
 
146
180
  // -----------------------------------------------------------------------
147
- // Modal: session-picker (src/input/modes/handlers/modal-session-picker.ts)
181
+ // Modal: session-picker
148
182
  // -----------------------------------------------------------------------
149
183
  .mode('modal.session-picker', (m) =>
150
184
  m
151
- .map('<Esc>', actions.sessionPickerEscape)
152
- .map('j', actions.moveModalSelection(1))
153
- .map('k', actions.moveModalSelection(-1))
185
+ .map('<Esc>', actions.sessionPickerEscape, 'Cancel')
186
+ .map('j', actions.moveModalSelection(1), 'Next')
187
+ .map('k', actions.moveModalSelection(-1), 'Prev')
154
188
  .map('<Down>', actions.moveModalSelection(1))
155
189
  .map('<Up>', actions.moveModalSelection(-1))
156
- .map('<CR>', actions.confirmSelectedSession)
157
- .map('n', actions.openCreateSessionModal)
158
- .map('r', actions.openRenameSelectedSession)
159
- .map('d', actions.deleteSelectedSession)
160
- .map('/', actions.beginSessionFilter)
190
+ .map('<CR>', actions.confirmSelectedSession, 'Open')
191
+ .map('n', actions.openCreateSessionModal, 'New')
192
+ .map('r', actions.openRenameSelectedSession, 'Rename')
193
+ .map('d', actions.deleteSelectedSession, 'Delete')
194
+ .map('/', actions.beginSessionFilter, 'Filter')
161
195
  )
162
196
 
163
197
  // -----------------------------------------------------------------------
164
198
  // Modal: session-picker filtering
165
- // (src/input/modes/handlers/modal-session-picker-filter.ts)
166
199
  // -----------------------------------------------------------------------
167
200
  .mode('modal.session-picker.filtering', (m) =>
168
201
  m
169
- .map('<Esc>', actions.cancelCommandEdit('modal.session-picker'))
170
- .map('<CR>', actions.confirmSelectedSession)
171
- .map('<C-n>', actions.moveModalSelection(1))
172
- .map('<C-p>', actions.moveModalSelection(-1))
202
+ .map('<Esc>', actions.cancelCommandEdit('modal.session-picker'), 'Cancel')
203
+ .map('<CR>', actions.confirmSelectedSession, 'Open')
204
+ .map('<C-n>', actions.moveModalSelection(1), 'Next')
205
+ .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
173
206
  .passthrough()
174
207
  )
175
208
 
176
209
  // -----------------------------------------------------------------------
177
- // Modal: session-name (src/input/modes/handlers/modal-session-name.ts)
210
+ // Modal: session-name
178
211
  // -----------------------------------------------------------------------
179
212
  .mode('modal.session-name', (m) =>
180
213
  m
181
- .map('<Esc>', actions.backToSessionPicker)
182
- .map('<CR>', actions.confirmSessionRename)
214
+ .map('<Esc>', actions.backToSessionPicker, 'Cancel')
215
+ .map('<CR>', actions.confirmSessionRename, 'Confirm')
183
216
  .passthrough()
184
217
  )
185
218
 
186
219
  // -----------------------------------------------------------------------
187
220
  // Modal: create-session
188
- // (src/input/modes/handlers/modal-create-session.ts)
189
221
  // -----------------------------------------------------------------------
190
222
  .mode('modal.create-session', (m) =>
191
223
  m
192
- .map('<Esc>', actions.backToSessionPicker)
193
- .map('<Tab>', actions.switchField)
194
- .map('<CR>', actions.confirmCreateSession)
195
- .map('<C-n>', actions.moveModalSelection(1))
196
- .map('<C-p>', actions.moveModalSelection(-1))
224
+ .map('<Esc>', actions.backToSessionPicker, 'Cancel')
225
+ .map('<Tab>', actions.switchField, 'Next field')
226
+ .map('<CR>', actions.confirmCreateSession, 'Confirm')
227
+ .map('<C-n>', actions.moveModalSelection(1), 'Next')
228
+ .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
197
229
  .passthrough()
198
230
  )
199
231
 
200
232
  // -----------------------------------------------------------------------
201
233
  // Modal: snippet-picker
202
- // (src/input/modes/handlers/modal-snippet-picker.ts)
203
234
  // -----------------------------------------------------------------------
204
235
  .mode('modal.snippet-picker', (m) =>
205
236
  m
206
- .map('<Esc>', actions.closeModal)
207
- .map('j', actions.moveModalSelection(1))
208
- .map('k', actions.moveModalSelection(-1))
237
+ .map('<Esc>', actions.closeModal, 'Cancel')
238
+ .map('j', actions.moveModalSelection(1), 'Next')
239
+ .map('k', actions.moveModalSelection(-1), 'Prev')
209
240
  .map('<Down>', actions.moveModalSelection(1))
210
241
  .map('<Up>', actions.moveModalSelection(-1))
211
- .map('<CR>', actions.pasteSelectedSnippet)
212
- .map('a', actions.pasteSnippetToGroup)
213
- .map('n', actions.openSnippetEditor)
214
- .map('r', actions.editSelectedSnippet)
242
+ .map('<CR>', actions.pasteSelectedSnippet, 'Send')
243
+ .map('a', actions.pasteSnippetToGroup, 'Send to group')
244
+ .map('n', actions.openSnippetEditor, 'New')
245
+ .map('r', actions.editSelectedSnippet, 'Edit')
215
246
  .map('e', actions.editSelectedSnippet)
216
- .map('d', actions.deleteSelectedSnippet)
217
- .map('/', actions.beginSnippetFilter)
247
+ .map('d', actions.deleteSelectedSnippet, 'Delete')
248
+ .map('/', actions.beginSnippetFilter, 'Filter')
218
249
  )
219
250
 
220
251
  // -----------------------------------------------------------------------
221
252
  // Modal: snippet-picker filtering
222
- // (src/input/modes/handlers/modal-snippet-picker-filter.ts)
223
253
  // -----------------------------------------------------------------------
224
254
  .mode('modal.snippet-picker.filtering', (m) =>
225
255
  m
226
- .map('<Esc>', actions.cancelCommandEdit('modal.snippet-picker'))
227
- .map('<CR>', actions.snippetFilterPaste)
228
- .map('<C-a>', actions.snippetFilterPasteToGroup)
229
- .map('<C-n>', actions.moveModalSelection(1))
230
- .map('<C-p>', actions.moveModalSelection(-1))
256
+ .map('<Esc>', actions.cancelCommandEdit('modal.snippet-picker'), 'Cancel')
257
+ .map('<CR>', actions.snippetFilterPaste, 'Send')
258
+ .map('<C-a>', actions.snippetFilterPasteToGroup, 'Send to group')
259
+ .map('<C-n>', actions.moveModalSelection(1), 'Next')
260
+ .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
231
261
  .passthrough()
232
262
  )
233
263
 
234
264
  // -----------------------------------------------------------------------
235
265
  // Modal: snippet-editor
236
- // (src/input/modes/handlers/modal-snippet-editor.ts)
237
266
  // -----------------------------------------------------------------------
238
267
  .mode('modal.snippet-editor', (m) =>
239
268
  m
240
- .map('<Esc>', actions.backToSnippetPicker)
241
- .map('<Tab>', actions.switchField)
242
- .map('<CR>', actions.saveSnippetEditor)
243
- .map('<C-n>', actions.moveModalSelection(1))
244
- .map('<C-p>', actions.moveModalSelection(-1))
269
+ .map('<Esc>', actions.backToSnippetPicker, 'Cancel')
270
+ .map('<Tab>', actions.switchField, 'Next field')
271
+ .map('<CR>', actions.saveSnippetEditor, 'Save')
272
+ .map('<C-n>', actions.moveModalSelection(1), 'Next')
273
+ .map('<C-p>', actions.moveModalSelection(-1), 'Prev')
245
274
  .passthrough()
246
275
  )
247
276
 
248
277
  // -----------------------------------------------------------------------
249
278
  // Modal: split-picker
250
- // (src/input/modes/handlers/modal-split-picker.ts)
251
279
  // -----------------------------------------------------------------------
252
280
  .mode('modal.split-picker', (m) =>
253
281
  m
254
- .map('<Esc>', actions.closeModal)
255
- .map('j', actions.moveModalSelection(1))
256
- .map('k', actions.moveModalSelection(-1))
282
+ .map('<Esc>', actions.closeModal, 'Cancel')
283
+ .map('j', actions.moveModalSelection(1), 'Next')
284
+ .map('k', actions.moveModalSelection(-1), 'Prev')
257
285
  .map('<Down>', actions.moveModalSelection(1))
258
286
  .map('<Up>', actions.moveModalSelection(-1))
259
- .map('<CR>', actions.confirmSplit)
287
+ .map('<CR>', actions.confirmSplit, 'Confirm')
260
288
  )
261
289
 
262
290
  // -----------------------------------------------------------------------
263
- // Modal: git-commit (src/input/modes/handlers/modal-git-commit.ts)
291
+ // Modal: git-commit
264
292
  // -----------------------------------------------------------------------
265
293
  .mode('modal.git-commit', (m) =>
266
294
  m
267
- .map('<Esc>', actions.gitCommitCancel)
268
- .map('<C-CR>', actions.gitCommitSubmit)
269
- .map('<Tab>', actions.switchField)
270
- .map('<CR>', actions.gitCommitReturnKey)
295
+ .map('<Esc>', actions.gitCommitCancel, 'Cancel')
296
+ .map('<C-CR>', actions.gitCommitSubmit, 'Commit')
297
+ .map('<Tab>', actions.switchField, 'Next field')
298
+ .map('<CR>', actions.gitCommitReturnKey, 'Newline / confirm')
271
299
  .passthrough()
272
300
  )
273
301
 
@@ -21,8 +21,13 @@ export class GroupBuilder implements GroupBuilderApi {
21
21
  private readonly groupName: string
22
22
  ) {}
23
23
 
24
- map(keys: string, action: Action): this {
25
- this.bindings.push({ group: this.groupName, keys: `${this.prefix}${keys}`, result: action })
24
+ map(keys: string, action: Action, description?: string): this {
25
+ this.bindings.push({
26
+ description,
27
+ group: this.groupName,
28
+ keys: `${this.prefix}${keys}`,
29
+ result: action,
30
+ })
26
31
  return this
27
32
  }
28
33
 
@@ -43,8 +48,8 @@ export class ModeBindingBuilder implements ModeBindingBuilderApi {
43
48
  private readonly removals: string[] = []
44
49
  private _passthrough = false
45
50
 
46
- map(keys: string, action: Action): this {
47
- this.bindings.push({ keys, result: action })
51
+ map(keys: string, action: Action, description?: string): this {
52
+ this.bindings.push({ description, keys, result: action })
48
53
  return this
49
54
  }
50
55
 
@@ -94,10 +99,36 @@ export class KeymapBuilder implements KeymapBuilderApi {
94
99
  return this
95
100
  }
96
101
 
97
- mode(id: ModeId, configure: (m: ModeBindingBuilderApi) => ModeBindingBuilderApi): this {
102
+ mode(
103
+ id: ModeId | readonly ModeId[],
104
+ configure: (m: ModeBindingBuilderApi) => ModeBindingBuilderApi
105
+ ): this {
98
106
  const builder = new ModeBindingBuilder()
99
107
  configure(builder)
100
- this.modes.set(id, builder._build())
108
+ const incoming = builder._build()
109
+ const ids = Array.isArray(id) ? id : [id]
110
+ for (const modeId of ids) {
111
+ const existing = this.modes.get(modeId)
112
+ if (!existing) {
113
+ // Fresh mode: clone so later merges don't mutate a shared array.
114
+ this.modes.set(modeId, {
115
+ bindings: [...incoming.bindings],
116
+ isPassthrough: incoming.isPassthrough,
117
+ removals: [...incoming.removals],
118
+ })
119
+ continue
120
+ }
121
+ // Merge: later bindings win over earlier ones sharing the same keys.
122
+ const incomingKeys = new Set(incoming.bindings.map((b) => b.keys))
123
+ this.modes.set(modeId, {
124
+ bindings: [
125
+ ...existing.bindings.filter((b) => !incomingKeys.has(b.keys)),
126
+ ...incoming.bindings,
127
+ ],
128
+ isPassthrough: existing.isPassthrough || incoming.isPassthrough,
129
+ removals: [...existing.removals, ...incoming.removals],
130
+ })
131
+ }
101
132
  return this
102
133
  }
103
134
 
package/src/resolver.ts CHANGED
@@ -20,6 +20,7 @@ export function resolveConfig(userConfig: AimuxUserConfig): ResolvedConfig {
20
20
  backends: userConfig.backends ?? {},
21
21
  hooks: userConfig.hooks ?? {},
22
22
  keymaps,
23
+ sessionBar: userConfig.sessionBar ?? {},
23
24
  sidebar: userConfig.sidebar ?? {},
24
25
  snippets: userConfig.snippets ?? [],
25
26
  theme: userConfig.theme,
package/src/types.ts CHANGED
@@ -27,6 +27,7 @@ export type ModeId =
27
27
  | 'modal.help'
28
28
  | 'modal.split-picker'
29
29
  | 'modal.git-commit'
30
+ | 'modal.update-available'
30
31
 
31
32
  // ─── Primitive app types ──────────────────────────────────────────────────────
32
33
 
@@ -124,6 +125,7 @@ export interface SessionRecord {
124
125
  createdAt: string
125
126
  updatedAt: string
126
127
  lastOpenedAt: string
128
+ order?: number
127
129
  workspaceSnapshot?: WorkspaceSnapshotV1
128
130
  }
129
131
 
@@ -263,6 +265,12 @@ export interface ModalSnippetEditor extends ModalBase {
263
265
  contentBuffer: string
264
266
  }
265
267
 
268
+ export interface ModalUpdateAvailable extends ModalBase {
269
+ type: 'update-available'
270
+ currentVersion: string
271
+ latestVersion: string
272
+ }
273
+
266
274
  export type ModalState =
267
275
  | ModalClosed
268
276
  | ModalNewTab
@@ -276,12 +284,20 @@ export type ModalState =
276
284
  | ModalCreateSession
277
285
  | ModalSnippetEditor
278
286
  | ModalGitCommit
287
+ | ModalUpdateAvailable
279
288
 
280
289
  export interface LayoutState {
281
290
  terminalCols: number
282
291
  terminalRows: number
283
292
  }
284
293
 
294
+ export type SessionBarPosition = 'top' | 'bottom'
295
+
296
+ export interface SessionBarState {
297
+ visible: boolean
298
+ position: SessionBarPosition
299
+ }
300
+
285
301
  export interface AppState {
286
302
  tabs: TabSession[]
287
303
  activeTabId: string | null
@@ -289,6 +305,8 @@ export interface AppState {
289
305
  tabGroupMap: Record<string, string>
290
306
  sessions: SessionRecord[]
291
307
  currentSessionId: string | null
308
+ sessionsBusy: Record<string, boolean>
309
+ sessionBar: SessionBarState
292
310
  snippets: SnippetRecord[]
293
311
  focusMode: FocusMode
294
312
  sidebar: SidebarState
@@ -324,6 +342,7 @@ export type ModalAction =
324
342
  | { type: 'open-snippet-editor'; snippetId?: string }
325
343
  | { type: 'begin-snippet-filter' }
326
344
  | { type: 'open-theme-picker' }
345
+ | { type: 'open-update-available-modal'; currentVersion: string; latestVersion: string }
327
346
 
328
347
  export type SessionAction =
329
348
  | { type: 'load-session'; sessionId: string; workspaceSnapshot?: WorkspaceSnapshotV1 }
@@ -331,6 +350,8 @@ export type SessionAction =
331
350
  | { type: 'create-session-record'; session: SessionRecord }
332
351
  | { type: 'rename-session-record'; sessionId: string; name: string }
333
352
  | { type: 'delete-session-record'; sessionId: string }
353
+ | { type: 'reorder-sessions'; orderedIds: string[] }
354
+ | { type: 'set-session-busy'; sessionId: string; busy: boolean }
334
355
 
335
356
  export type TabAction =
336
357
  | { type: 'add-tab'; tab: TabSession }
@@ -392,6 +413,8 @@ export type UIAction =
392
413
  | { type: 'toggle-git-panel' }
393
414
  | { type: 'resize-git-panel'; delta: number }
394
415
  | { type: 'set-pending-chords'; chords: string[] | null }
416
+ | { type: 'toggle-session-bar' }
417
+ | { type: 'set-session-bar-position'; position: SessionBarPosition }
395
418
 
396
419
  export interface GitRefreshPayload {
397
420
  branch: string | null
@@ -467,6 +490,8 @@ export type SideEffect =
467
490
  | { type: 'git-rm'; path: string }
468
491
  | { type: 'git-commit'; title: string; body: string }
469
492
  | { type: 'git-push' }
493
+ | { type: 'confirm-update-selection' }
494
+ | { type: 'switch-session-by-index'; index: number }
470
495
 
471
496
  // ─── Key input / KeyResult / ModeContext ──────────────────────────────────────
472
497
 
@@ -563,7 +588,7 @@ export type Action = KeyResult | ActionFn
563
588
  // ─── Keymap builder API types ─────────────────────────────────────────────────
564
589
 
565
590
  export interface GroupBuilderApi {
566
- map(keys: string, action: Action): GroupBuilderApi
591
+ map(keys: string, action: Action, description?: string): GroupBuilderApi
567
592
  group(
568
593
  prefix: string,
569
594
  name: string,
@@ -572,7 +597,7 @@ export interface GroupBuilderApi {
572
597
  }
573
598
 
574
599
  export interface ModeBindingBuilderApi {
575
- map(keys: string, action: Action): ModeBindingBuilderApi
600
+ map(keys: string, action: Action, description?: string): ModeBindingBuilderApi
576
601
  unmap(keys: string): ModeBindingBuilderApi
577
602
  group(
578
603
  prefix: string,
@@ -585,16 +610,25 @@ export interface ModeBindingBuilderApi {
585
610
  export interface KeymapBuilderApi {
586
611
  leader(key: string): KeymapBuilderApi
587
612
  timeout(ms: number): KeymapBuilderApi
588
- mode(id: ModeId, configure: (m: ModeBindingBuilderApi) => ModeBindingBuilderApi): KeymapBuilderApi
613
+ mode(
614
+ id: ModeId | readonly ModeId[],
615
+ configure: (m: ModeBindingBuilderApi) => ModeBindingBuilderApi
616
+ ): KeymapBuilderApi
589
617
  }
590
618
 
591
619
  // ─── Top-level user config ────────────────────────────────────────────────────
592
620
 
621
+ export interface SessionBarConfig {
622
+ visible?: boolean
623
+ position?: SessionBarPosition
624
+ }
625
+
593
626
  export interface AimuxUserConfig {
594
627
  theme?: ThemeId | ThemeDefinition
595
628
  keymaps?: (k: KeymapBuilderApi) => KeymapBuilderApi
596
629
  backends?: Record<string, BackendConfig>
597
630
  sidebar?: SidebarConfig
631
+ sessionBar?: SessionBarConfig
598
632
  hooks?: HooksConfig
599
633
  snippets?: SnippetDef[]
600
634
  }
@@ -605,6 +639,7 @@ export interface BindingDef {
605
639
  keys: string
606
640
  result: Action
607
641
  group?: string
642
+ description?: string
608
643
  }
609
644
 
610
645
  export interface ModeKeymapDef {
@@ -624,6 +659,7 @@ export interface ResolvedConfig {
624
659
  keymaps: ResolvedKeymapConfig
625
660
  backends: Record<string, BackendConfig>
626
661
  sidebar: SidebarConfig
662
+ sessionBar: SessionBarConfig
627
663
  hooks: HooksConfig
628
664
  snippets: SnippetDef[]
629
665
  }