@brainfile/cli 0.9.2 → 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/CHANGELOG.md +36 -0
- package/README.md +30 -4
- package/dist/commands/tui.d.ts +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +8 -2
- package/dist/commands/tui.js.map +1 -1
- package/dist/tui/BrainfileTUI.d.ts.map +1 -1
- package/dist/tui/BrainfileTUI.js +47 -14
- package/dist/tui/BrainfileTUI.js.map +1 -1
- package/dist/tui/actions.d.ts +32 -2
- package/dist/tui/actions.d.ts.map +1 -1
- package/dist/tui/actions.js +274 -2
- package/dist/tui/actions.js.map +1 -1
- package/dist/tui/components/ArchivePanel.d.ts +16 -0
- package/dist/tui/components/ArchivePanel.d.ts.map +1 -0
- package/dist/tui/components/ArchivePanel.js +172 -0
- package/dist/tui/components/ArchivePanel.js.map +1 -0
- package/dist/tui/components/HelpOverlay.d.ts.map +1 -1
- package/dist/tui/components/HelpOverlay.js +43 -27
- package/dist/tui/components/HelpOverlay.js.map +1 -1
- package/dist/tui/components/MainPanelTabs.d.ts +9 -0
- package/dist/tui/components/MainPanelTabs.d.ts.map +1 -0
- package/dist/tui/components/MainPanelTabs.js +37 -0
- package/dist/tui/components/MainPanelTabs.js.map +1 -0
- package/dist/tui/components/RulesPanel.d.ts +24 -0
- package/dist/tui/components/RulesPanel.d.ts.map +1 -0
- package/dist/tui/components/RulesPanel.js +125 -0
- package/dist/tui/components/RulesPanel.js.map +1 -0
- package/dist/tui/components/index.d.ts +6 -0
- package/dist/tui/components/index.d.ts.map +1 -1
- package/dist/tui/components/index.js +7 -1
- package/dist/tui/components/index.js.map +1 -1
- package/dist/tui/hooks/useKeyboardNavigation.d.ts.map +1 -1
- package/dist/tui/hooks/useKeyboardNavigation.js +516 -190
- package/dist/tui/hooks/useKeyboardNavigation.js.map +1 -1
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/theme.d.ts +6 -0
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +6 -0
- package/dist/tui/theme.js.map +1 -1
- package/dist/tui/types.d.ts +16 -2
- package/dist/tui/types.d.ts.map +1 -1
- package/dist/tui/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.useKeyboardNavigation = useKeyboardNavigation;
|
|
4
4
|
const ink_1 = require("ink");
|
|
5
5
|
const actions_js_1 = require("../actions.js");
|
|
6
|
+
const RULE_TYPES = ['always', 'never', 'prefer', 'context'];
|
|
6
7
|
// Helper to show status message
|
|
7
8
|
function showStatus(setState, text, type) {
|
|
8
9
|
const timestamp = Date.now();
|
|
@@ -173,7 +174,7 @@ function useKeyboardNavigation({ state, setState, currentTasks, maxTaskIndex, fi
|
|
|
173
174
|
}
|
|
174
175
|
return;
|
|
175
176
|
}
|
|
176
|
-
// Search mode handling
|
|
177
|
+
// Search mode handling (Tasks panel)
|
|
177
178
|
if (state.mode === 'search') {
|
|
178
179
|
if (key.escape) {
|
|
179
180
|
setState(prev => ({ ...prev, mode: 'browse', searchQuery: '' }));
|
|
@@ -193,7 +194,162 @@ function useKeyboardNavigation({ state, setState, currentTasks, maxTaskIndex, fi
|
|
|
193
194
|
}
|
|
194
195
|
return;
|
|
195
196
|
}
|
|
196
|
-
//
|
|
197
|
+
// Rule add/edit mode
|
|
198
|
+
if (state.mode === 'rule-add' || state.mode === 'rule-edit') {
|
|
199
|
+
if (key.escape) {
|
|
200
|
+
setState(prev => ({ ...prev, mode: 'browse', ruleEditText: '', ruleEditId: null }));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key.return) {
|
|
204
|
+
const text = state.ruleEditText.trim();
|
|
205
|
+
if (!text) {
|
|
206
|
+
showStatus(setState, 'Rule text required', 'error');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (state.mode === 'rule-add') {
|
|
210
|
+
const result = (0, actions_js_1.addRuleAction)(filePath, state.activeRuleType, text);
|
|
211
|
+
if (result.success) {
|
|
212
|
+
showStatus(setState, result.message || 'Rule added', 'success');
|
|
213
|
+
loadBrainfile(true);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
showStatus(setState, result.error || 'Failed to add rule', 'error');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (state.ruleEditId !== null) {
|
|
220
|
+
const result = (0, actions_js_1.updateRuleAction)(filePath, state.activeRuleType, state.ruleEditId, text);
|
|
221
|
+
if (result.success) {
|
|
222
|
+
showStatus(setState, result.message || 'Rule updated', 'success');
|
|
223
|
+
loadBrainfile(true);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
showStatus(setState, result.error || 'Failed to update rule', 'error');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setState(prev => ({ ...prev, mode: 'browse', ruleEditText: '', ruleEditId: null }));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (key.backspace || key.delete) {
|
|
233
|
+
setState(prev => ({ ...prev, ruleEditText: prev.ruleEditText.slice(0, -1) }));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (input && !key.ctrl && !key.meta) {
|
|
237
|
+
setState(prev => ({ ...prev, ruleEditText: prev.ruleEditText + input }));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
// Rule delete confirmation
|
|
243
|
+
if (state.mode === 'rule-delete-confirm') {
|
|
244
|
+
const rules = state.board?.rules?.[state.activeRuleType] || [];
|
|
245
|
+
const rule = rules[state.selectedRuleIndex];
|
|
246
|
+
if (input === 'y' || input === 'Y') {
|
|
247
|
+
if (rule) {
|
|
248
|
+
const result = (0, actions_js_1.deleteRuleAction)(filePath, state.activeRuleType, rule.id);
|
|
249
|
+
if (result.success) {
|
|
250
|
+
showStatus(setState, result.message || 'Rule deleted', 'success');
|
|
251
|
+
loadBrainfile(true);
|
|
252
|
+
// Adjust selection if needed
|
|
253
|
+
setState(prev => ({
|
|
254
|
+
...prev,
|
|
255
|
+
mode: 'browse',
|
|
256
|
+
selectedRuleIndex: Math.max(0, prev.selectedRuleIndex - 1),
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
showStatus(setState, result.error || 'Failed to delete rule', 'error');
|
|
261
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
267
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
268
|
+
showStatus(setState, 'Delete cancelled', 'info');
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// Archive restore mode - column picker
|
|
274
|
+
if (state.mode === 'archive-restore') {
|
|
275
|
+
if (key.escape) {
|
|
276
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (key.downArrow || input === 'j') {
|
|
280
|
+
setState(prev => ({
|
|
281
|
+
...prev,
|
|
282
|
+
archiveRestoreColumnIndex: Math.min(prev.archiveRestoreColumnIndex + 1, allColumns.length - 1),
|
|
283
|
+
}));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (key.upArrow || input === 'k') {
|
|
287
|
+
setState(prev => ({
|
|
288
|
+
...prev,
|
|
289
|
+
archiveRestoreColumnIndex: Math.max(prev.archiveRestoreColumnIndex - 1, 0),
|
|
290
|
+
}));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (key.return) {
|
|
294
|
+
const task = state.archive[state.selectedArchiveIndex];
|
|
295
|
+
const column = allColumns[state.archiveRestoreColumnIndex];
|
|
296
|
+
if (task && column) {
|
|
297
|
+
const result = (0, actions_js_1.restoreTaskAction)(filePath, task.id, column.id);
|
|
298
|
+
if (result.success) {
|
|
299
|
+
showStatus(setState, result.message || 'Task restored', 'success');
|
|
300
|
+
loadBrainfile(true);
|
|
301
|
+
// Reload archive
|
|
302
|
+
const archiveResult = (0, actions_js_1.loadArchive)(filePath);
|
|
303
|
+
setState(prev => ({
|
|
304
|
+
...prev,
|
|
305
|
+
mode: 'browse',
|
|
306
|
+
archive: archiveResult.archive,
|
|
307
|
+
selectedArchiveIndex: Math.max(0, prev.selectedArchiveIndex - 1),
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
showStatus(setState, result.error || 'Failed to restore task', 'error');
|
|
312
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
// Archive delete confirmation
|
|
320
|
+
if (state.mode === 'archive-delete-confirm') {
|
|
321
|
+
const task = state.archive[state.selectedArchiveIndex];
|
|
322
|
+
if (input === 'y' || input === 'Y') {
|
|
323
|
+
if (task) {
|
|
324
|
+
const result = (0, actions_js_1.deleteArchivedTaskAction)(filePath, task.id);
|
|
325
|
+
if (result.success) {
|
|
326
|
+
showStatus(setState, result.message || 'Task permanently deleted', 'success');
|
|
327
|
+
// Reload archive
|
|
328
|
+
const archiveResult = (0, actions_js_1.loadArchive)(filePath);
|
|
329
|
+
setState(prev => ({
|
|
330
|
+
...prev,
|
|
331
|
+
mode: 'browse',
|
|
332
|
+
archive: archiveResult.archive,
|
|
333
|
+
selectedArchiveIndex: Math.max(0, prev.selectedArchiveIndex - 1),
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
showStatus(setState, result.error || 'Failed to delete task', 'error');
|
|
338
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
344
|
+
setState(prev => ({ ...prev, mode: 'browse' }));
|
|
345
|
+
showStatus(setState, 'Delete cancelled', 'info');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// ============================================================
|
|
351
|
+
// GLOBAL KEYS (work in browse mode across all panels)
|
|
352
|
+
// ============================================================
|
|
197
353
|
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
198
354
|
exit();
|
|
199
355
|
return;
|
|
@@ -202,232 +358,402 @@ function useKeyboardNavigation({ state, setState, currentTasks, maxTaskIndex, fi
|
|
|
202
358
|
setState(prev => ({ ...prev, mode: 'help' }));
|
|
203
359
|
return;
|
|
204
360
|
}
|
|
205
|
-
|
|
206
|
-
|
|
361
|
+
// Panel switching: 1/2/3
|
|
362
|
+
if (input === '1') {
|
|
363
|
+
setState(prev => ({ ...prev, activePanel: 'tasks', mode: 'browse' }));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (input === '2') {
|
|
367
|
+
setState(prev => ({ ...prev, activePanel: 'rules', mode: 'browse' }));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (input === '3') {
|
|
371
|
+
setState(prev => ({ ...prev, activePanel: 'archive', mode: 'browse' }));
|
|
372
|
+
// Archive is loaded via useEffect in BrainfileTUI
|
|
207
373
|
return;
|
|
208
374
|
}
|
|
209
375
|
// Refresh (force refresh bypasses hash check)
|
|
210
|
-
if (input === 'r') {
|
|
376
|
+
if (input === 'r' && state.activePanel !== 'archive') {
|
|
211
377
|
loadBrainfile(true);
|
|
212
378
|
showStatus(setState, 'Refreshed', 'info');
|
|
213
379
|
return;
|
|
214
380
|
}
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
381
|
+
// ============================================================
|
|
382
|
+
// TASKS PANEL - Browse mode
|
|
383
|
+
// ============================================================
|
|
384
|
+
if (state.activePanel === 'tasks') {
|
|
385
|
+
if (input === '/') {
|
|
386
|
+
setState(prev => ({ ...prev, mode: 'search', searchQuery: '' }));
|
|
220
387
|
return;
|
|
221
388
|
}
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
389
|
+
// 'e' - Edit task in $EDITOR
|
|
390
|
+
if (input === 'e') {
|
|
391
|
+
if (!currentTask) {
|
|
392
|
+
showStatus(setState, 'No task selected', 'error');
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const result = (0, actions_js_1.editTaskInEditor)(filePath, currentTask.id);
|
|
396
|
+
if (result.success) {
|
|
397
|
+
showStatus(setState, result.message || 'Task updated', 'success');
|
|
398
|
+
loadBrainfile(true);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
showStatus(setState, result.error || 'Edit failed', 'error');
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
227
404
|
}
|
|
228
|
-
|
|
229
|
-
|
|
405
|
+
// 'm' - Move task
|
|
406
|
+
if (input === 'm') {
|
|
407
|
+
if (!currentTask) {
|
|
408
|
+
showStatus(setState, 'No task selected', 'error');
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
setState(prev => ({ ...prev, mode: 'move', moveTargetIndex: prev.activeColumnIndex }));
|
|
412
|
+
return;
|
|
230
413
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
414
|
+
// 'd' - Delete task
|
|
415
|
+
if (input === 'd') {
|
|
416
|
+
if (!currentTask) {
|
|
417
|
+
showStatus(setState, 'No task selected', 'error');
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
setState(prev => ({ ...prev, mode: 'delete-confirm' }));
|
|
237
421
|
return;
|
|
238
422
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
423
|
+
// 'A' - Archive task
|
|
424
|
+
if (input === 'A') {
|
|
425
|
+
if (!currentTask) {
|
|
426
|
+
showStatus(setState, 'No task selected', 'error');
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const result = (0, actions_js_1.archiveTaskAction)(filePath, currentTask.id);
|
|
430
|
+
if (result.success) {
|
|
431
|
+
showStatus(setState, result.message || 'Task archived', 'success');
|
|
432
|
+
loadBrainfile(true);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
showStatus(setState, result.error || 'Archive failed', 'error');
|
|
436
|
+
}
|
|
246
437
|
return;
|
|
247
438
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
439
|
+
// 'p' - Cycle priority
|
|
440
|
+
if (input === 'p') {
|
|
441
|
+
if (!currentTask) {
|
|
442
|
+
showStatus(setState, 'No task selected', 'error');
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const result = (0, actions_js_1.cyclePriorityAction)(filePath, currentTask.id);
|
|
446
|
+
if (result.success) {
|
|
447
|
+
showStatus(setState, result.message || 'Priority updated', 'success');
|
|
448
|
+
loadBrainfile(true);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
showStatus(setState, result.error || 'Failed to update priority', 'error');
|
|
452
|
+
}
|
|
255
453
|
return;
|
|
256
454
|
}
|
|
257
|
-
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
455
|
+
// 't' - Toggle subtask
|
|
456
|
+
if (input === 't') {
|
|
457
|
+
if (!currentTask) {
|
|
458
|
+
showStatus(setState, 'No task selected', 'error');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (!currentTask.subtasks || currentTask.subtasks.length === 0) {
|
|
462
|
+
showStatus(setState, 'No subtasks', 'info');
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
setState(prev => ({ ...prev, mode: 'subtask', selectedSubtaskIndex: 0 }));
|
|
466
|
+
return;
|
|
261
467
|
}
|
|
262
|
-
|
|
263
|
-
|
|
468
|
+
// 'y' - Copy task ID
|
|
469
|
+
if (input === 'y') {
|
|
470
|
+
if (!currentTask) {
|
|
471
|
+
showStatus(setState, 'No task selected', 'error');
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const result = (0, actions_js_1.copyToClipboard)(currentTask.id);
|
|
475
|
+
if (result.success) {
|
|
476
|
+
showStatus(setState, `Copied ${currentTask.id}`, 'success');
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
showStatus(setState, result.error || 'Copy failed', 'error');
|
|
480
|
+
}
|
|
481
|
+
return;
|
|
264
482
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (input === 'p') {
|
|
269
|
-
if (!currentTask) {
|
|
270
|
-
showStatus(setState, 'No task selected', 'error');
|
|
483
|
+
// 'n' - New task (quick)
|
|
484
|
+
if (input === 'n') {
|
|
485
|
+
setState(prev => ({ ...prev, mode: 'new-task', newTaskTitle: '' }));
|
|
271
486
|
return;
|
|
272
487
|
}
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
|
|
488
|
+
// 'N' - New task (editor)
|
|
489
|
+
if (input === 'N') {
|
|
490
|
+
const currentCol = allColumns[state.activeColumnIndex];
|
|
491
|
+
if (currentCol) {
|
|
492
|
+
const result = (0, actions_js_1.newTaskInEditor)(filePath, currentCol.id);
|
|
493
|
+
if (result.success) {
|
|
494
|
+
showStatus(setState, result.message || 'Task created', 'success');
|
|
495
|
+
loadBrainfile(true);
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
showStatus(setState, result.error || 'Failed to create task', 'error');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return;
|
|
277
502
|
}
|
|
278
|
-
|
|
279
|
-
|
|
503
|
+
// Navigation: up/down
|
|
504
|
+
if (key.downArrow || input === 'j') {
|
|
505
|
+
setState(prev => ({
|
|
506
|
+
...prev,
|
|
507
|
+
selectedTaskIndex: Math.min(prev.selectedTaskIndex + 1, maxTaskIndex),
|
|
508
|
+
}));
|
|
509
|
+
return;
|
|
280
510
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
showStatus(setState, 'No task selected', 'error');
|
|
511
|
+
if (key.upArrow || input === 'k') {
|
|
512
|
+
setState(prev => ({
|
|
513
|
+
...prev,
|
|
514
|
+
selectedTaskIndex: Math.max(prev.selectedTaskIndex - 1, 0),
|
|
515
|
+
}));
|
|
287
516
|
return;
|
|
288
517
|
}
|
|
289
|
-
|
|
290
|
-
|
|
518
|
+
// Page scrolling
|
|
519
|
+
if (key.ctrl && input === 'd') {
|
|
520
|
+
setState(prev => ({
|
|
521
|
+
...prev,
|
|
522
|
+
selectedTaskIndex: Math.min(prev.selectedTaskIndex + Math.floor(viewportHeight / 2), maxTaskIndex),
|
|
523
|
+
}));
|
|
291
524
|
return;
|
|
292
525
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
526
|
+
if (key.ctrl && input === 'u') {
|
|
527
|
+
setState(prev => ({
|
|
528
|
+
...prev,
|
|
529
|
+
selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0),
|
|
530
|
+
}));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
// Column switching
|
|
534
|
+
if (key.tab || key.rightArrow || input === 'l') {
|
|
535
|
+
setState(prev => ({
|
|
536
|
+
...prev,
|
|
537
|
+
activeColumnIndex: (prev.activeColumnIndex + 1) % filteredColumnsLength,
|
|
538
|
+
selectedTaskIndex: 0,
|
|
539
|
+
}));
|
|
300
540
|
return;
|
|
301
541
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
542
|
+
if ((key.shift && key.tab) || key.leftArrow || input === 'h') {
|
|
543
|
+
setState(prev => ({
|
|
544
|
+
...prev,
|
|
545
|
+
activeColumnIndex: prev.activeColumnIndex === 0
|
|
546
|
+
? filteredColumnsLength - 1
|
|
547
|
+
: prev.activeColumnIndex - 1,
|
|
548
|
+
selectedTaskIndex: 0,
|
|
549
|
+
}));
|
|
550
|
+
return;
|
|
305
551
|
}
|
|
306
|
-
|
|
307
|
-
|
|
552
|
+
// Home/End
|
|
553
|
+
if (input === 'g') {
|
|
554
|
+
setState(prev => ({ ...prev, selectedTaskIndex: 0 }));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (input === 'G') {
|
|
558
|
+
setState(prev => ({ ...prev, selectedTaskIndex: maxTaskIndex }));
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
// Expand/collapse task
|
|
562
|
+
if (key.return) {
|
|
563
|
+
const task = currentTasks[state.selectedTaskIndex];
|
|
564
|
+
if (task) {
|
|
565
|
+
setState(prev => {
|
|
566
|
+
const newExpanded = new Set(prev.expandedTaskIds);
|
|
567
|
+
if (newExpanded.has(task.id)) {
|
|
568
|
+
newExpanded.delete(task.id);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
newExpanded.add(task.id);
|
|
572
|
+
}
|
|
573
|
+
return { ...prev, expandedTaskIds: newExpanded };
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
// Escape: collapse all or clear search
|
|
579
|
+
if (key.escape) {
|
|
580
|
+
setState(prev => ({
|
|
581
|
+
...prev,
|
|
582
|
+
expandedTaskIds: new Set(),
|
|
583
|
+
searchQuery: '',
|
|
584
|
+
}));
|
|
585
|
+
return;
|
|
308
586
|
}
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
// 'n' - New task (quick add - title only)
|
|
312
|
-
if (input === 'n') {
|
|
313
|
-
setState(prev => ({ ...prev, mode: 'new-task', newTaskTitle: '' }));
|
|
314
|
-
return;
|
|
315
587
|
}
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
588
|
+
// ============================================================
|
|
589
|
+
// RULES PANEL - Browse mode
|
|
590
|
+
// ============================================================
|
|
591
|
+
if (state.activePanel === 'rules') {
|
|
592
|
+
const rules = state.board?.rules?.[state.activeRuleType] || [];
|
|
593
|
+
const maxRuleIndex = Math.max(0, rules.length - 1);
|
|
594
|
+
// h/l or left/right to switch rule type
|
|
595
|
+
if (key.leftArrow || input === 'h') {
|
|
596
|
+
const currentIdx = RULE_TYPES.indexOf(state.activeRuleType);
|
|
597
|
+
const newIdx = currentIdx === 0 ? RULE_TYPES.length - 1 : currentIdx - 1;
|
|
598
|
+
setState(prev => ({ ...prev, activeRuleType: RULE_TYPES[newIdx], selectedRuleIndex: 0 }));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (key.rightArrow || input === 'l') {
|
|
602
|
+
const currentIdx = RULE_TYPES.indexOf(state.activeRuleType);
|
|
603
|
+
const newIdx = (currentIdx + 1) % RULE_TYPES.length;
|
|
604
|
+
setState(prev => ({ ...prev, activeRuleType: RULE_TYPES[newIdx], selectedRuleIndex: 0 }));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
// j/k to navigate rules
|
|
608
|
+
if (key.downArrow || input === 'j') {
|
|
609
|
+
setState(prev => ({
|
|
610
|
+
...prev,
|
|
611
|
+
selectedRuleIndex: Math.min(prev.selectedRuleIndex + 1, maxRuleIndex),
|
|
612
|
+
}));
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (key.upArrow || input === 'k') {
|
|
616
|
+
setState(prev => ({
|
|
617
|
+
...prev,
|
|
618
|
+
selectedRuleIndex: Math.max(prev.selectedRuleIndex - 1, 0),
|
|
619
|
+
}));
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
// n - New rule
|
|
623
|
+
if (input === 'n') {
|
|
624
|
+
setState(prev => ({ ...prev, mode: 'rule-add', ruleEditText: '', ruleEditId: null }));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
// e - Edit rule
|
|
628
|
+
if (input === 'e') {
|
|
629
|
+
const rule = rules[state.selectedRuleIndex];
|
|
630
|
+
if (rule) {
|
|
631
|
+
setState(prev => ({
|
|
632
|
+
...prev,
|
|
633
|
+
mode: 'rule-edit',
|
|
634
|
+
ruleEditText: rule.rule,
|
|
635
|
+
ruleEditId: rule.id,
|
|
636
|
+
}));
|
|
324
637
|
}
|
|
325
638
|
else {
|
|
326
|
-
showStatus(setState,
|
|
639
|
+
showStatus(setState, 'No rule selected', 'error');
|
|
327
640
|
}
|
|
641
|
+
return;
|
|
328
642
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
if (key.ctrl && input === 'u') {
|
|
355
|
-
setState(prev => ({
|
|
356
|
-
...prev,
|
|
357
|
-
selectedTaskIndex: Math.max(prev.selectedTaskIndex - Math.floor(viewportHeight / 2), 0),
|
|
358
|
-
}));
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
// Column switching: TAB / left/right
|
|
362
|
-
if (key.tab || key.rightArrow || input === 'l') {
|
|
363
|
-
setState(prev => ({
|
|
364
|
-
...prev,
|
|
365
|
-
activeColumnIndex: (prev.activeColumnIndex + 1) % filteredColumnsLength,
|
|
366
|
-
selectedTaskIndex: 0,
|
|
367
|
-
}));
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if ((key.shift && key.tab) || key.leftArrow || input === 'h') {
|
|
371
|
-
setState(prev => ({
|
|
372
|
-
...prev,
|
|
373
|
-
activeColumnIndex: prev.activeColumnIndex === 0
|
|
374
|
-
? filteredColumnsLength - 1
|
|
375
|
-
: prev.activeColumnIndex - 1,
|
|
376
|
-
selectedTaskIndex: 0,
|
|
377
|
-
}));
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
// Jump to column headers with { and }
|
|
381
|
-
if (input === '{') {
|
|
382
|
-
setState(prev => ({
|
|
383
|
-
...prev,
|
|
384
|
-
activeColumnIndex: Math.max(0, prev.activeColumnIndex - 1),
|
|
385
|
-
selectedTaskIndex: 0,
|
|
386
|
-
}));
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (input === '}') {
|
|
390
|
-
setState(prev => ({
|
|
391
|
-
...prev,
|
|
392
|
-
activeColumnIndex: Math.min(filteredColumnsLength - 1, prev.activeColumnIndex + 1),
|
|
393
|
-
selectedTaskIndex: 0,
|
|
394
|
-
}));
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
// Home/End
|
|
398
|
-
if (input === 'g') {
|
|
399
|
-
setState(prev => ({ ...prev, selectedTaskIndex: 0 }));
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (input === 'G') {
|
|
403
|
-
setState(prev => ({ ...prev, selectedTaskIndex: maxTaskIndex }));
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
// Expand/collapse task
|
|
407
|
-
if (key.return) {
|
|
408
|
-
const task = currentTasks[state.selectedTaskIndex];
|
|
409
|
-
if (task) {
|
|
410
|
-
setState(prev => {
|
|
411
|
-
const newExpanded = new Set(prev.expandedTaskIds);
|
|
412
|
-
if (newExpanded.has(task.id)) {
|
|
413
|
-
newExpanded.delete(task.id);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
newExpanded.add(task.id);
|
|
417
|
-
}
|
|
418
|
-
return { ...prev, expandedTaskIds: newExpanded };
|
|
419
|
-
});
|
|
643
|
+
// d - Delete rule
|
|
644
|
+
if (input === 'd') {
|
|
645
|
+
const rule = rules[state.selectedRuleIndex];
|
|
646
|
+
if (rule) {
|
|
647
|
+
setState(prev => ({ ...prev, mode: 'rule-delete-confirm' }));
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
showStatus(setState, 'No rule selected', 'error');
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Home/End
|
|
655
|
+
if (input === 'g') {
|
|
656
|
+
setState(prev => ({ ...prev, selectedRuleIndex: 0 }));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (input === 'G') {
|
|
660
|
+
setState(prev => ({ ...prev, selectedRuleIndex: maxRuleIndex }));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
// Escape
|
|
664
|
+
if (key.escape) {
|
|
665
|
+
setState(prev => ({ ...prev, selectedRuleIndex: 0 }));
|
|
666
|
+
return;
|
|
420
667
|
}
|
|
421
|
-
return;
|
|
422
668
|
}
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
669
|
+
// ============================================================
|
|
670
|
+
// ARCHIVE PANEL - Browse mode
|
|
671
|
+
// ============================================================
|
|
672
|
+
if (state.activePanel === 'archive') {
|
|
673
|
+
const maxArchiveIndex = Math.max(0, state.archive.length - 1);
|
|
674
|
+
// / - Search archive
|
|
675
|
+
if (input === '/') {
|
|
676
|
+
// Archive search is inline in the panel, just focus on it
|
|
677
|
+
// For simplicity, we'll clear and let user type
|
|
678
|
+
setState(prev => ({ ...prev, archiveSearchQuery: '' }));
|
|
679
|
+
showStatus(setState, 'Type to search archive', 'info');
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
// r - Refresh archive
|
|
683
|
+
if (input === 'r') {
|
|
684
|
+
const result = (0, actions_js_1.loadArchive)(filePath);
|
|
685
|
+
setState(prev => ({ ...prev, archive: result.archive }));
|
|
686
|
+
showStatus(setState, 'Archive refreshed', 'info');
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
// j/k navigation
|
|
690
|
+
if (key.downArrow || input === 'j') {
|
|
691
|
+
setState(prev => ({
|
|
692
|
+
...prev,
|
|
693
|
+
selectedArchiveIndex: Math.min(prev.selectedArchiveIndex + 1, maxArchiveIndex),
|
|
694
|
+
}));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (key.upArrow || input === 'k') {
|
|
698
|
+
setState(prev => ({
|
|
699
|
+
...prev,
|
|
700
|
+
selectedArchiveIndex: Math.max(prev.selectedArchiveIndex - 1, 0),
|
|
701
|
+
}));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
// Enter - Expand/collapse
|
|
705
|
+
if (key.return) {
|
|
706
|
+
const task = state.archive[state.selectedArchiveIndex];
|
|
707
|
+
if (task) {
|
|
708
|
+
setState(prev => {
|
|
709
|
+
const newExpanded = new Set(prev.expandedArchiveIds);
|
|
710
|
+
if (newExpanded.has(task.id)) {
|
|
711
|
+
newExpanded.delete(task.id);
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
newExpanded.add(task.id);
|
|
715
|
+
}
|
|
716
|
+
return { ...prev, expandedArchiveIds: newExpanded };
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
// r key for restore (use 'R' to avoid conflict with refresh)
|
|
722
|
+
if (input === 'R') {
|
|
723
|
+
if (state.archive.length === 0) {
|
|
724
|
+
showStatus(setState, 'No archived tasks', 'error');
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
setState(prev => ({ ...prev, mode: 'archive-restore', archiveRestoreColumnIndex: 0 }));
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
// d - Delete permanently
|
|
731
|
+
if (input === 'd') {
|
|
732
|
+
if (state.archive.length === 0) {
|
|
733
|
+
showStatus(setState, 'No archived tasks', 'error');
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
setState(prev => ({ ...prev, mode: 'archive-delete-confirm' }));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
// Home/End
|
|
740
|
+
if (input === 'g') {
|
|
741
|
+
setState(prev => ({ ...prev, selectedArchiveIndex: 0 }));
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (input === 'G') {
|
|
745
|
+
setState(prev => ({ ...prev, selectedArchiveIndex: maxArchiveIndex }));
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
// Escape
|
|
749
|
+
if (key.escape) {
|
|
750
|
+
setState(prev => ({
|
|
751
|
+
...prev,
|
|
752
|
+
expandedArchiveIds: new Set(),
|
|
753
|
+
archiveSearchQuery: '',
|
|
754
|
+
}));
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
431
757
|
}
|
|
432
758
|
});
|
|
433
759
|
}
|