floq 0.2.2 → 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.
Files changed (37) hide show
  1. package/README.ja.md +13 -0
  2. package/README.md +13 -0
  3. package/dist/i18n/en.d.ts +16 -0
  4. package/dist/i18n/en.js +10 -1
  5. package/dist/i18n/ja.js +10 -1
  6. package/dist/ui/App.js +132 -64
  7. package/dist/ui/SplashScreen.js +1 -1
  8. package/dist/ui/components/HelpModal.js +3 -2
  9. package/dist/ui/components/KanbanBoard.js +128 -46
  10. package/dist/ui/components/SearchResults.js +2 -2
  11. package/dist/ui/history/HistoryContext.d.ts +29 -0
  12. package/dist/ui/history/HistoryContext.js +44 -0
  13. package/dist/ui/history/HistoryManager.d.ts +56 -0
  14. package/dist/ui/history/HistoryManager.js +137 -0
  15. package/dist/ui/history/commands/ConvertToProjectCommand.d.ts +19 -0
  16. package/dist/ui/history/commands/ConvertToProjectCommand.js +37 -0
  17. package/dist/ui/history/commands/CreateCommentCommand.d.ts +18 -0
  18. package/dist/ui/history/commands/CreateCommentCommand.js +23 -0
  19. package/dist/ui/history/commands/CreateTaskCommand.d.ts +18 -0
  20. package/dist/ui/history/commands/CreateTaskCommand.js +24 -0
  21. package/dist/ui/history/commands/DeleteCommentCommand.d.ts +17 -0
  22. package/dist/ui/history/commands/DeleteCommentCommand.js +26 -0
  23. package/dist/ui/history/commands/DeleteTaskCommand.d.ts +18 -0
  24. package/dist/ui/history/commands/DeleteTaskCommand.js +51 -0
  25. package/dist/ui/history/commands/LinkTaskCommand.d.ts +20 -0
  26. package/dist/ui/history/commands/LinkTaskCommand.js +37 -0
  27. package/dist/ui/history/commands/MoveTaskCommand.d.ts +25 -0
  28. package/dist/ui/history/commands/MoveTaskCommand.js +43 -0
  29. package/dist/ui/history/commands/index.d.ts +7 -0
  30. package/dist/ui/history/commands/index.js +7 -0
  31. package/dist/ui/history/index.d.ts +6 -0
  32. package/dist/ui/history/index.js +8 -0
  33. package/dist/ui/history/types.d.ts +26 -0
  34. package/dist/ui/history/types.js +4 -0
  35. package/dist/ui/history/useHistory.d.ts +24 -0
  36. package/dist/ui/history/useHistory.js +31 -0
  37. package/package.json +1 -1
package/README.ja.md CHANGED
@@ -61,9 +61,20 @@ floq
61
61
  | `Esc/b` | 戻る |
62
62
  | `/` | タスク検索 |
63
63
  | `r` | 更新 |
64
+ | `u` | 元に戻す(Undo) |
65
+ | `Ctrl+r` | やり直し(Redo) |
64
66
  | `?` | ヘルプ |
65
67
  | `q` | 終了 |
66
68
 
69
+ #### 検索
70
+
71
+ | キー | アクション |
72
+ |------|-----------|
73
+ | `/` | 検索モード開始 |
74
+ | `↑/↓` または `Ctrl+j/k` | 検索結果をナビゲート |
75
+ | `Enter` | 選択したタスクのタブに移動して選択 |
76
+ | `Esc` | キャンセル |
77
+
67
78
  #### プロジェクト詳細画面
68
79
 
69
80
  | キー | アクション |
@@ -98,6 +109,8 @@ floq
98
109
  | `Enter` | タスク詳細を開く |
99
110
  | `/` | タスク検索 |
100
111
  | `r` | 更新 |
112
+ | `u` | 元に戻す(Undo) |
113
+ | `Ctrl+r` | やり直し(Redo) |
101
114
  | `?` | ヘルプ |
102
115
  | `q` | 終了 |
103
116
 
package/README.md CHANGED
@@ -61,9 +61,20 @@ floq
61
61
  | `Esc/b` | Back |
62
62
  | `/` | Search tasks |
63
63
  | `r` | Refresh |
64
+ | `u` | Undo |
65
+ | `Ctrl+r` | Redo |
64
66
  | `?` | Help |
65
67
  | `q` | Quit |
66
68
 
69
+ #### Search
70
+
71
+ | Key | Action |
72
+ |-----|--------|
73
+ | `/` | Start search mode |
74
+ | `↑/↓` or `Ctrl+j/k` | Navigate search results |
75
+ | `Enter` | Jump to selected task's tab and select it |
76
+ | `Esc` | Cancel search |
77
+
67
78
  #### Project Detail View
68
79
 
69
80
  | Key | Action |
@@ -98,6 +109,8 @@ floq
98
109
  | `Enter` | Open task detail |
99
110
  | `/` | Search tasks |
100
111
  | `r` | Refresh |
112
+ | `u` | Undo |
113
+ | `Ctrl+r` | Redo |
101
114
  | `?` | Help |
102
115
  | `q` | Quit |
103
116
 
package/dist/i18n/en.d.ts CHANGED
@@ -136,6 +136,8 @@ export declare const en: {
136
136
  other: string;
137
137
  showHelp: string;
138
138
  quit: string;
139
+ undo: string;
140
+ redo: string;
139
141
  closeHint: string;
140
142
  };
141
143
  whatsNew: {
@@ -181,6 +183,12 @@ export declare const en: {
181
183
  deleteConfirm: string;
182
184
  deleted: string;
183
185
  deleteCancelled: string;
186
+ undone: string;
187
+ redone: string;
188
+ nothingToUndo: string;
189
+ nothingToRedo: string;
190
+ undoFailed: string;
191
+ redoFailed: string;
184
192
  comments: string;
185
193
  projectTasks: string;
186
194
  search: {
@@ -289,6 +297,8 @@ export type HelpTranslations = {
289
297
  other: string;
290
298
  showHelp: string;
291
299
  quit: string;
300
+ undo: string;
301
+ redo: string;
292
302
  closeHint: string;
293
303
  };
294
304
  export type WhatsNewTranslations = {
@@ -407,6 +417,12 @@ export type TuiTranslations = {
407
417
  deleteConfirm: string;
408
418
  deleted: string;
409
419
  deleteCancelled: string;
420
+ undone: string;
421
+ redone: string;
422
+ nothingToUndo: string;
423
+ nothingToRedo: string;
424
+ undoFailed: string;
425
+ redoFailed: string;
410
426
  comments: string;
411
427
  projectTasks: string;
412
428
  };
package/dist/i18n/en.js CHANGED
@@ -145,6 +145,8 @@ export const en = {
145
145
  other: 'Other',
146
146
  showHelp: 'Show this help',
147
147
  quit: 'Quit',
148
+ undo: 'Undo',
149
+ redo: 'Redo',
148
150
  closeHint: 'Esc/q: close',
149
151
  },
150
152
  // What's New / Changelog
@@ -192,13 +194,20 @@ export const en = {
192
194
  deleteConfirm: 'Delete "{title}"? (y/n)',
193
195
  deleted: 'Deleted: "{title}"',
194
196
  deleteCancelled: 'Delete cancelled',
197
+ // Undo/Redo
198
+ undone: 'Undone: {action}',
199
+ redone: 'Redone: {action}',
200
+ nothingToUndo: 'Nothing to undo',
201
+ nothingToRedo: 'Nothing to redo',
202
+ undoFailed: 'Undo failed',
203
+ redoFailed: 'Redo failed',
195
204
  comments: 'Comments',
196
205
  projectTasks: 'Tasks',
197
206
  // Search
198
207
  search: {
199
208
  prefix: '/',
200
209
  placeholder: 'Search tasks...',
201
- help: '(Enter to select, Ctrl+j/k to navigate, Esc to cancel)',
210
+ help: '(Enter to select, ↑/↓ or Ctrl+j/k to navigate, Esc to cancel)',
202
211
  noResults: 'No matching tasks',
203
212
  resultsTitle: 'Search Results',
204
213
  searchTasks: 'Search tasks',
package/dist/i18n/ja.js CHANGED
@@ -145,6 +145,8 @@ export const ja = {
145
145
  other: 'その他',
146
146
  showHelp: 'このヘルプを表示',
147
147
  quit: '終了',
148
+ undo: '元に戻す',
149
+ redo: 'やり直し',
148
150
  closeHint: 'Esc/q: 閉じる',
149
151
  },
150
152
  // What's New / Changelog
@@ -192,13 +194,20 @@ export const ja = {
192
194
  deleteConfirm: '「{title}」を削除しますか? (y/n)',
193
195
  deleted: '削除しました: 「{title}」',
194
196
  deleteCancelled: '削除をキャンセルしました',
197
+ // Undo/Redo
198
+ undone: '元に戻しました: {action}',
199
+ redone: 'やり直しました: {action}',
200
+ nothingToUndo: '元に戻す操作がありません',
201
+ nothingToRedo: 'やり直す操作がありません',
202
+ undoFailed: '元に戻す操作に失敗しました',
203
+ redoFailed: 'やり直しに失敗しました',
195
204
  comments: 'コメント',
196
205
  projectTasks: 'タスク一覧',
197
206
  // Search
198
207
  search: {
199
208
  prefix: '/',
200
209
  placeholder: 'タスクを検索...',
201
- help: '(Enterで選択, Ctrl+j/kで移動, Escでキャンセル)',
210
+ help: '(Enterで選択, ↑/↓またはCtrl+j/kで移動, Escでキャンセル)',
202
211
  noResults: '該当するタスクがありません',
203
212
  resultsTitle: '検索結果',
204
213
  searchTasks: 'タスク検索',
package/dist/ui/App.js CHANGED
@@ -19,6 +19,7 @@ import { ThemeProvider, useTheme } from './theme/index.js';
19
19
  import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled } from '../config.js';
20
20
  import { KanbanBoard } from './components/KanbanBoard.js';
21
21
  import { VERSION } from '../version.js';
22
+ import { HistoryProvider, useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, } from './history/index.js';
22
23
  const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
23
24
  export function App() {
24
25
  const [themeName, setThemeNameState] = useState(getThemeName);
@@ -53,11 +54,12 @@ export function App() {
53
54
  if (settingsMode === 'lang-select') {
54
55
  return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(LanguageSelector, { onSelect: handleLocaleSelect, onCancel: handleSettingsCancel }) }));
55
56
  }
56
- return (_jsx(ThemeProvider, { themeName: themeName, children: viewMode === 'kanban' ? (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode })) }));
57
+ return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(HistoryProvider, { children: viewMode === 'kanban' ? (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode })) }) }));
57
58
  }
58
59
  function AppContent({ onOpenSettings }) {
59
60
  const theme = useTheme();
60
61
  const { exit } = useApp();
62
+ const history = useHistory();
61
63
  const [mode, setMode] = useState('splash');
62
64
  const [inputValue, setInputValue] = useState('');
63
65
  const [currentListIndex, setCurrentListIndex] = useState(0);
@@ -174,42 +176,64 @@ function AppContent({ onOpenSettings }) {
174
176
  setSearchResults(results);
175
177
  setSearchResultIndex(0);
176
178
  }, [searchTasks]);
179
+ // Navigate to a task from search results
180
+ const navigateToTask = useCallback((task) => {
181
+ const targetTab = task.isProject ? 'projects' : task.status;
182
+ const tabIndex = TABS.indexOf(targetTab);
183
+ const tabTasks = tasks[targetTab];
184
+ const taskIndex = tabTasks.findIndex(t => t.id === task.id);
185
+ if (tabIndex >= 0 && taskIndex >= 0) {
186
+ setCurrentListIndex(tabIndex);
187
+ setSelectedTaskIndex(taskIndex);
188
+ setMode('normal');
189
+ }
190
+ }, [tasks]);
177
191
  const addTask = useCallback(async (title, parentId) => {
178
192
  if (!title.trim())
179
193
  return;
180
- const db = getDb();
181
194
  const now = new Date();
182
- await db.insert(schema.tasks)
183
- .values({
184
- id: uuidv4(),
185
- title: title.trim(),
186
- status: parentId ? 'next' : 'inbox',
187
- parentId: parentId || null,
188
- createdAt: now,
189
- updatedAt: now,
195
+ const taskId = uuidv4();
196
+ const command = new CreateTaskCommand({
197
+ task: {
198
+ id: taskId,
199
+ title: title.trim(),
200
+ status: parentId ? 'next' : 'inbox',
201
+ parentId: parentId || null,
202
+ createdAt: now,
203
+ updatedAt: now,
204
+ },
205
+ description: fmt(i18n.tui.added, { title: title.trim() }),
190
206
  });
207
+ await history.execute(command);
191
208
  setMessage(fmt(i18n.tui.added, { title: title.trim() }));
192
209
  await loadTasks();
193
- }, [i18n.tui.added, loadTasks]);
210
+ }, [i18n.tui.added, loadTasks, history]);
194
211
  const addCommentToTask = useCallback(async (task, content) => {
195
- const db = getDb();
196
- await db.insert(schema.comments).values({
197
- id: uuidv4(),
198
- taskId: task.id,
199
- content: content.trim(),
200
- createdAt: new Date(),
212
+ const commentId = uuidv4();
213
+ const command = new CreateCommentCommand({
214
+ comment: {
215
+ id: commentId,
216
+ taskId: task.id,
217
+ content: content.trim(),
218
+ createdAt: new Date(),
219
+ },
220
+ description: i18n.tui.commentAdded || 'Comment added',
201
221
  });
222
+ await history.execute(command);
202
223
  setMessage(i18n.tui.commentAdded || 'Comment added');
203
224
  await loadTaskComments(task.id);
204
- }, [i18n.tui.commentAdded, loadTaskComments]);
225
+ }, [i18n.tui.commentAdded, loadTaskComments, history]);
205
226
  const deleteComment = useCallback(async (comment) => {
206
- const db = getDb();
207
- await db.delete(schema.comments).where(eq(schema.comments.id, comment.id));
227
+ const command = new DeleteCommentCommand({
228
+ comment,
229
+ description: i18n.tui.commentDeleted || 'Comment deleted',
230
+ });
231
+ await history.execute(command);
208
232
  setMessage(i18n.tui.commentDeleted || 'Comment deleted');
209
233
  if (selectedTask) {
210
234
  await loadTaskComments(selectedTask.id);
211
235
  }
212
- }, [i18n.tui.commentDeleted, loadTaskComments, selectedTask]);
236
+ }, [i18n.tui.commentDeleted, loadTaskComments, selectedTask, history]);
213
237
  const handleInputSubmit = async (value) => {
214
238
  if (mode === 'move-to-waiting' && taskToWaiting) {
215
239
  if (value.trim()) {
@@ -224,9 +248,7 @@ function AppContent({ onOpenSettings }) {
224
248
  if (mode === 'search') {
225
249
  if (searchResults.length > 0) {
226
250
  const task = searchResults[searchResultIndex];
227
- setSelectedTask(task);
228
- loadTaskComments(task.id);
229
- setMode('task-detail');
251
+ navigateToTask(task);
230
252
  }
231
253
  else {
232
254
  setMode('normal');
@@ -262,54 +284,74 @@ function AppContent({ onOpenSettings }) {
262
284
  setInputValue('');
263
285
  };
264
286
  const linkTaskToProject = useCallback(async (task, project) => {
265
- const db = getDb();
266
- await db.update(schema.tasks)
267
- .set({ parentId: project.id, updatedAt: new Date() })
268
- .where(eq(schema.tasks.id, task.id));
287
+ const command = new LinkTaskCommand({
288
+ taskId: task.id,
289
+ fromParentId: task.parentId,
290
+ toParentId: project.id,
291
+ description: fmt(i18n.tui.linkedToProject || 'Linked "{title}" to {project}', { title: task.title, project: project.title }),
292
+ });
293
+ await history.execute(command);
269
294
  setMessage(fmt(i18n.tui.linkedToProject || 'Linked "{title}" to {project}', { title: task.title, project: project.title }));
270
295
  await loadTasks();
271
- }, [i18n.tui.linkedToProject, loadTasks]);
296
+ }, [i18n.tui.linkedToProject, loadTasks, history]);
272
297
  const markTaskDone = useCallback(async (task) => {
273
- const db = getDb();
274
- await db.update(schema.tasks)
275
- .set({ status: 'done', updatedAt: new Date() })
276
- .where(eq(schema.tasks.id, task.id));
298
+ const command = new MoveTaskCommand({
299
+ taskId: task.id,
300
+ fromStatus: task.status,
301
+ toStatus: 'done',
302
+ fromWaitingFor: task.waitingFor,
303
+ toWaitingFor: null,
304
+ description: fmt(i18n.tui.completed, { title: task.title }),
305
+ });
306
+ await history.execute(command);
277
307
  setMessage(fmt(i18n.tui.completed, { title: task.title }));
278
308
  await loadTasks();
279
- }, [i18n.tui.completed, loadTasks]);
309
+ }, [i18n.tui.completed, loadTasks, history]);
280
310
  const moveTaskToStatus = useCallback(async (task, status) => {
281
- const db = getDb();
282
- await db.update(schema.tasks)
283
- .set({ status, waitingFor: null, updatedAt: new Date() })
284
- .where(eq(schema.tasks.id, task.id));
311
+ const command = new MoveTaskCommand({
312
+ taskId: task.id,
313
+ fromStatus: task.status,
314
+ toStatus: status,
315
+ fromWaitingFor: task.waitingFor,
316
+ toWaitingFor: null,
317
+ description: fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }),
318
+ });
319
+ await history.execute(command);
285
320
  setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }));
286
321
  await loadTasks();
287
- }, [i18n.tui.movedTo, i18n.status, loadTasks]);
322
+ }, [i18n.tui.movedTo, i18n.status, loadTasks, history]);
288
323
  const moveTaskToWaiting = useCallback(async (task, waitingFor) => {
289
- const db = getDb();
290
- await db.update(schema.tasks)
291
- .set({ status: 'waiting', waitingFor: waitingFor.trim(), updatedAt: new Date() })
292
- .where(eq(schema.tasks.id, task.id));
324
+ const command = new MoveTaskCommand({
325
+ taskId: task.id,
326
+ fromStatus: task.status,
327
+ toStatus: 'waiting',
328
+ fromWaitingFor: task.waitingFor,
329
+ toWaitingFor: waitingFor.trim(),
330
+ description: fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }),
331
+ });
332
+ await history.execute(command);
293
333
  setMessage(fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }));
294
334
  await loadTasks();
295
- }, [i18n.tui.movedToWaiting, loadTasks]);
335
+ }, [i18n.tui.movedToWaiting, loadTasks, history]);
296
336
  const makeTaskProject = useCallback(async (task) => {
297
- const db = getDb();
298
- await db.update(schema.tasks)
299
- .set({ isProject: true, status: 'next', updatedAt: new Date() })
300
- .where(eq(schema.tasks.id, task.id));
337
+ const command = new ConvertToProjectCommand({
338
+ taskId: task.id,
339
+ originalStatus: task.status,
340
+ description: fmt(i18n.tui.madeProject || 'Made project: {title}', { title: task.title }),
341
+ });
342
+ await history.execute(command);
301
343
  setMessage(fmt(i18n.tui.madeProject || 'Made project: {title}', { title: task.title }));
302
344
  await loadTasks();
303
- }, [i18n.tui.madeProject, loadTasks]);
345
+ }, [i18n.tui.madeProject, loadTasks, history]);
304
346
  const deleteTask = useCallback(async (task) => {
305
- const db = getDb();
306
- // Delete comments first
307
- await db.delete(schema.comments).where(eq(schema.comments.taskId, task.id));
308
- // Delete the task
309
- await db.delete(schema.tasks).where(eq(schema.tasks.id, task.id));
347
+ const command = new DeleteTaskCommand({
348
+ task,
349
+ description: fmt(i18n.tui.deleted || 'Deleted: "{title}"', { title: task.title }),
350
+ });
351
+ await history.execute(command);
310
352
  setMessage(fmt(i18n.tui.deleted || 'Deleted: "{title}"', { title: task.title }));
311
353
  await loadTasks();
312
- }, [i18n.tui.deleted, loadTasks]);
354
+ }, [i18n.tui.deleted, loadTasks, history]);
313
355
  const getTabLabel = (tab) => {
314
356
  switch (tab) {
315
357
  case 'inbox':
@@ -332,10 +374,6 @@ function AppContent({ onOpenSettings }) {
332
374
  setMode('normal');
333
375
  return;
334
376
  }
335
- // Handle help mode - let HelpModal handle input
336
- if (mode === 'help') {
337
- return;
338
- }
339
377
  // Handle search mode
340
378
  if (mode === 'search') {
341
379
  if (key.escape) {
@@ -345,12 +383,12 @@ function AppContent({ onOpenSettings }) {
345
383
  setMode('normal');
346
384
  return;
347
385
  }
348
- // Navigate search results with Ctrl+j/k or Ctrl+n/p
349
- if (key.ctrl && (input === 'j' || input === 'n')) {
386
+ // Navigate search results with arrow keys, Ctrl+j/k, or Ctrl+n/p
387
+ if (key.downArrow || (key.ctrl && (input === 'j' || input === 'n'))) {
350
388
  setSearchResultIndex((prev) => prev < searchResults.length - 1 ? prev + 1 : 0);
351
389
  return;
352
390
  }
353
- if (key.ctrl && (input === 'k' || input === 'p')) {
391
+ if (key.upArrow || (key.ctrl && (input === 'k' || input === 'p'))) {
354
392
  setSearchResultIndex((prev) => prev > 0 ? prev - 1 : Math.max(0, searchResults.length - 1));
355
393
  return;
356
394
  }
@@ -750,12 +788,42 @@ function AppContent({ onOpenSettings }) {
750
788
  return;
751
789
  }
752
790
  // Refresh
753
- if (input === 'r') {
791
+ if (input === 'r' && !(key.ctrl)) {
754
792
  loadTasks();
755
793
  setMessage(i18n.tui.refreshed);
756
794
  return;
757
795
  }
758
- });
796
+ // Undo (u key) - only in normal mode
797
+ if (input === 'u' && mode === 'normal') {
798
+ history.undo().then((didUndo) => {
799
+ if (didUndo) {
800
+ setMessage(fmt(i18n.tui.undone, { action: history.undoDescription || '' }));
801
+ loadTasks();
802
+ }
803
+ else {
804
+ setMessage(i18n.tui.nothingToUndo);
805
+ }
806
+ }).catch(() => {
807
+ setMessage(i18n.tui.undoFailed);
808
+ });
809
+ return;
810
+ }
811
+ // Redo (Ctrl+r) - only in normal mode
812
+ if (key.ctrl && input === 'r' && mode === 'normal') {
813
+ history.redo().then((didRedo) => {
814
+ if (didRedo) {
815
+ setMessage(fmt(i18n.tui.redone, { action: history.redoDescription || '' }));
816
+ loadTasks();
817
+ }
818
+ else {
819
+ setMessage(i18n.tui.nothingToRedo);
820
+ }
821
+ }).catch(() => {
822
+ setMessage(i18n.tui.redoFailed);
823
+ });
824
+ return;
825
+ }
826
+ }, { isActive: mode !== 'help' });
759
827
  // Splash screen
760
828
  if (mode === 'splash') {
761
829
  return _jsx(SplashScreen, { onComplete: () => setMode('normal') });
@@ -57,5 +57,5 @@ export function SplashScreen({ onComplete, duration = 1500 }) {
57
57
  const visibleLines = Math.min(Math.floor(frame * logoLines.length / 10), logoLines.length);
58
58
  return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: logoLines.slice(0, visibleLines).map((line, index) => (_jsx(Text, { color: theme.colors.secondary, bold: true, children: line }, index))) }), showTagline && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, italic: !isDosStyle, children: isDosStyle ? `[ ${TAGLINE.toUpperCase()} ]` : TAGLINE }) })), _jsx(Box, { marginTop: 2, children: _jsx(Text, { color: theme.colors.primary, children: frame < 10
59
59
  ? filled.repeat(frame) + empty.repeat(10 - frame)
60
- : filled.repeat(10) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, dimColor: !isDosStyle, children: isDosStyle ? `VER ${VERSION}` : `v${VERSION}` }) })] }));
60
+ : filled.repeat(10) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: isDosStyle ? theme.colors.textMuted : theme.colors.muted, children: isDosStyle ? `VER ${VERSION}` : `v${VERSION}` }) })] }));
61
61
  }
@@ -87,6 +87,7 @@ export function HelpModal({ onClose, isKanban = false }) {
87
87
  // Close modal
88
88
  if (key.escape || key.return || input === 'q' || input === ' ') {
89
89
  onClose();
90
+ return;
90
91
  }
91
92
  });
92
93
  const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
@@ -99,14 +100,14 @@ function GTDKeybindingsContent() {
99
100
  const help = i18n.tui.help;
100
101
  const theme = useTheme();
101
102
  const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
102
- return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-6" }), " ", help.tabSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.prevNextTab] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "n" }), " ", help.moveToNext] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "s" }), " ", help.moveToSomeday] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "w" }), " ", help.moveToWaiting] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "i" }), " ", help.moveToInbox] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "r" }), " ", help.refresh] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.projects) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "p" }), " ", help.makeProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "P" }), " ", help.linkToProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.openProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Esc/b" }), " ", help.backFromProject] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
103
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-6" }), " ", help.tabSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.prevNextTab] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "n" }), " ", help.moveToNext] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "s" }), " ", help.moveToSomeday] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "w" }), " ", help.moveToWaiting] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "i" }), " ", help.moveToInbox] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "r" }), " ", help.refresh] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "u" }), " ", help.undo] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Ctrl+r" }), " ", help.redo] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.projects) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "p" }), " ", help.makeProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "P" }), " ", help.linkToProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.openProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Esc/b" }), " ", help.backFromProject] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
103
104
  }
104
105
  function KanbanKeybindingsContent() {
105
106
  const i18n = t();
106
107
  const help = i18n.tui.kanbanHelp;
107
108
  const theme = useTheme();
108
109
  const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
109
- return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.columnSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-3" }), " ", help.columnDirect] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.moveRight] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "BS" }), " ", help.moveLeft] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
110
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.columnSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-3" }), " ", help.columnDirect] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.moveRight] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "BS" }), " ", help.moveLeft] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "u" }), " Undo"] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Ctrl+r" }), " Redo"] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "T" }), " ", help.changeTheme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "V" }), " ", help.changeViewMode] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "L" }), " ", help.changeLanguage] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
110
111
  }
111
112
  function InfoContent() {
112
113
  const i18n = t();