floq 0.1.0 → 0.2.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 (42) hide show
  1. package/README.ja.md +89 -9
  2. package/README.md +89 -9
  3. package/dist/changelog.d.ts +13 -0
  4. package/dist/changelog.js +95 -0
  5. package/dist/cli.js +44 -1
  6. package/dist/commands/comment.d.ts +2 -0
  7. package/dist/commands/comment.js +67 -0
  8. package/dist/commands/config.d.ts +4 -0
  9. package/dist/commands/config.js +123 -1
  10. package/dist/commands/setup.d.ts +1 -0
  11. package/dist/commands/setup.js +13 -0
  12. package/dist/config.d.ts +5 -0
  13. package/dist/config.js +40 -3
  14. package/dist/db/index.js +20 -0
  15. package/dist/db/schema.d.ts +83 -0
  16. package/dist/db/schema.js +6 -0
  17. package/dist/i18n/en.d.ts +237 -0
  18. package/dist/i18n/en.js +127 -3
  19. package/dist/i18n/ja.js +127 -3
  20. package/dist/index.js +14 -4
  21. package/dist/paths.d.ts +4 -0
  22. package/dist/paths.js +63 -5
  23. package/dist/ui/App.js +280 -25
  24. package/dist/ui/ModeSelector.d.ts +7 -0
  25. package/dist/ui/ModeSelector.js +37 -0
  26. package/dist/ui/SetupWizard.d.ts +6 -0
  27. package/dist/ui/SetupWizard.js +321 -0
  28. package/dist/ui/components/HelpModal.d.ts +2 -1
  29. package/dist/ui/components/HelpModal.js +118 -4
  30. package/dist/ui/components/KanbanBoard.d.ts +6 -0
  31. package/dist/ui/components/KanbanBoard.js +508 -0
  32. package/dist/ui/components/KanbanColumn.d.ts +12 -0
  33. package/dist/ui/components/KanbanColumn.js +11 -0
  34. package/dist/ui/components/ProgressBar.d.ts +7 -0
  35. package/dist/ui/components/ProgressBar.js +13 -0
  36. package/dist/ui/components/SearchBar.d.ts +8 -0
  37. package/dist/ui/components/SearchBar.js +11 -0
  38. package/dist/ui/components/SearchResults.d.ts +9 -0
  39. package/dist/ui/components/SearchResults.js +18 -0
  40. package/dist/ui/components/TaskItem.d.ts +6 -1
  41. package/dist/ui/components/TaskItem.js +3 -2
  42. package/package.json +1 -1
package/dist/ui/App.js CHANGED
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useEffect, useCallback } from 'react';
3
3
  import { Box, Text, useInput, useApp } from 'ink';
4
4
  import TextInput from 'ink-text-input';
@@ -7,15 +7,20 @@ import { v4 as uuidv4 } from 'uuid';
7
7
  import { TaskItem } from './components/TaskItem.js';
8
8
  import { HelpModal } from './components/HelpModal.js';
9
9
  import { FunctionKeyBar } from './components/FunctionKeyBar.js';
10
+ import { SearchBar } from './components/SearchBar.js';
11
+ import { SearchResults } from './components/SearchResults.js';
10
12
  import { SplashScreen } from './SplashScreen.js';
11
13
  import { getDb, schema } from '../db/index.js';
12
14
  import { t, fmt } from '../i18n/index.js';
13
15
  import { ThemeProvider, useTheme } from './theme/index.js';
14
- import { getThemeName, isTursoEnabled, getTursoConfig } from '../config.js';
15
- const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects'];
16
+ import { getThemeName, getViewMode, isTursoEnabled, getTursoConfig } from '../config.js';
17
+ import { KanbanBoard } from './components/KanbanBoard.js';
18
+ import { VERSION } from '../version.js';
19
+ const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
16
20
  export function App() {
17
21
  const themeName = getThemeName();
18
- return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(AppContent, {}) }));
22
+ const viewMode = getViewMode();
23
+ return (_jsx(ThemeProvider, { themeName: themeName, children: viewMode === 'kanban' ? _jsx(KanbanBoard, {}) : _jsx(AppContent, {}) }));
19
24
  }
20
25
  function AppContent() {
21
26
  const theme = useTheme();
@@ -30,12 +35,22 @@ function AppContent() {
30
35
  waiting: [],
31
36
  someday: [],
32
37
  projects: [],
38
+ done: [],
33
39
  });
34
40
  const [message, setMessage] = useState(null);
35
41
  const [selectedProject, setSelectedProject] = useState(null);
36
42
  const [projectTasks, setProjectTasks] = useState([]);
37
43
  const [taskToLink, setTaskToLink] = useState(null);
38
44
  const [projectSelectIndex, setProjectSelectIndex] = useState(0);
45
+ const [selectedTask, setSelectedTask] = useState(null);
46
+ const [taskComments, setTaskComments] = useState([]);
47
+ const [selectedCommentIndex, setSelectedCommentIndex] = useState(0);
48
+ const [taskToWaiting, setTaskToWaiting] = useState(null);
49
+ const [projectProgress, setProjectProgress] = useState({});
50
+ // Search state
51
+ const [searchQuery, setSearchQuery] = useState('');
52
+ const [searchResults, setSearchResults] = useState([]);
53
+ const [searchResultIndex, setSearchResultIndex] = useState(0);
39
54
  const i18n = t();
40
55
  const loadTasks = useCallback(async () => {
41
56
  const db = getDb();
@@ -45,9 +60,10 @@ function AppContent() {
45
60
  waiting: [],
46
61
  someday: [],
47
62
  projects: [],
63
+ done: [],
48
64
  };
49
65
  // Load all tasks (including project children) by status
50
- const statusList = ['inbox', 'next', 'waiting', 'someday'];
66
+ const statusList = ['inbox', 'next', 'waiting', 'someday', 'done'];
51
67
  for (const status of statusList) {
52
68
  newTasks[status] = await db
53
69
  .select()
@@ -59,6 +75,18 @@ function AppContent() {
59
75
  .select()
60
76
  .from(schema.tasks)
61
77
  .where(and(eq(schema.tasks.isProject, true), eq(schema.tasks.status, 'next')));
78
+ // Calculate progress for each project
79
+ const progress = {};
80
+ for (const project of newTasks.projects) {
81
+ const children = await db
82
+ .select()
83
+ .from(schema.tasks)
84
+ .where(eq(schema.tasks.parentId, project.id));
85
+ const total = children.length;
86
+ const completed = children.filter(t => t.status === 'done').length;
87
+ progress[project.id] = { completed, total };
88
+ }
89
+ setProjectProgress(progress);
62
90
  setTasks(newTasks);
63
91
  }, []);
64
92
  // Get parent project for a task
@@ -75,11 +103,43 @@ function AppContent() {
75
103
  .where(eq(schema.tasks.parentId, projectId));
76
104
  setProjectTasks(children);
77
105
  }, []);
106
+ const loadTaskComments = useCallback(async (taskId) => {
107
+ const db = getDb();
108
+ const comments = await db
109
+ .select()
110
+ .from(schema.comments)
111
+ .where(eq(schema.comments.taskId, taskId));
112
+ setTaskComments(comments);
113
+ }, []);
78
114
  useEffect(() => {
79
115
  loadTasks();
80
116
  }, [loadTasks]);
81
117
  const currentTab = TABS[currentListIndex];
82
118
  const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
119
+ // Get all tasks for search (across all statuses)
120
+ const getAllTasks = useCallback(() => {
121
+ const allTasks = [];
122
+ for (const tab of TABS) {
123
+ allTasks.push(...tasks[tab]);
124
+ }
125
+ return allTasks;
126
+ }, [tasks]);
127
+ // Search tasks by query
128
+ const searchTasks = useCallback((query) => {
129
+ if (!query.trim())
130
+ return [];
131
+ const lowerQuery = query.toLowerCase();
132
+ const allTasks = getAllTasks();
133
+ return allTasks.filter(task => task.title.toLowerCase().includes(lowerQuery) ||
134
+ (task.description && task.description.toLowerCase().includes(lowerQuery)));
135
+ }, [getAllTasks]);
136
+ // Handle search query change
137
+ const handleSearchChange = useCallback((value) => {
138
+ setSearchQuery(value);
139
+ const results = searchTasks(value);
140
+ setSearchResults(results);
141
+ setSearchResultIndex(0);
142
+ }, [searchTasks]);
83
143
  const addTask = useCallback(async (title, parentId) => {
84
144
  if (!title.trim())
85
145
  return;
@@ -97,9 +157,57 @@ function AppContent() {
97
157
  setMessage(fmt(i18n.tui.added, { title: title.trim() }));
98
158
  await loadTasks();
99
159
  }, [i18n.tui.added, loadTasks]);
160
+ const addCommentToTask = useCallback(async (task, content) => {
161
+ const db = getDb();
162
+ await db.insert(schema.comments).values({
163
+ id: uuidv4(),
164
+ taskId: task.id,
165
+ content: content.trim(),
166
+ createdAt: new Date(),
167
+ });
168
+ setMessage(i18n.tui.commentAdded || 'Comment added');
169
+ await loadTaskComments(task.id);
170
+ }, [i18n.tui.commentAdded, loadTaskComments]);
171
+ const deleteComment = useCallback(async (comment) => {
172
+ const db = getDb();
173
+ await db.delete(schema.comments).where(eq(schema.comments.id, comment.id));
174
+ setMessage(i18n.tui.commentDeleted || 'Comment deleted');
175
+ if (selectedTask) {
176
+ await loadTaskComments(selectedTask.id);
177
+ }
178
+ }, [i18n.tui.commentDeleted, loadTaskComments, selectedTask]);
100
179
  const handleInputSubmit = async (value) => {
180
+ if (mode === 'move-to-waiting' && taskToWaiting) {
181
+ if (value.trim()) {
182
+ await moveTaskToWaiting(taskToWaiting, value);
183
+ }
184
+ setTaskToWaiting(null);
185
+ setMode('normal');
186
+ setInputValue('');
187
+ return;
188
+ }
189
+ // Handle search mode submit
190
+ if (mode === 'search') {
191
+ if (searchResults.length > 0) {
192
+ const task = searchResults[searchResultIndex];
193
+ setSelectedTask(task);
194
+ loadTaskComments(task.id);
195
+ setMode('task-detail');
196
+ }
197
+ else {
198
+ setMode('normal');
199
+ }
200
+ setSearchQuery('');
201
+ setSearchResults([]);
202
+ setSearchResultIndex(0);
203
+ return;
204
+ }
101
205
  if (value.trim()) {
102
- if (mode === 'add-to-project' && selectedProject) {
206
+ if (mode === 'add-comment' && selectedTask) {
207
+ await addCommentToTask(selectedTask, value);
208
+ setMode('task-detail');
209
+ }
210
+ else if (mode === 'add-to-project' && selectedProject) {
103
211
  await addTask(value, selectedProject.id);
104
212
  await loadProjectTasks(selectedProject.id);
105
213
  setMode('project-detail');
@@ -110,7 +218,12 @@ function AppContent() {
110
218
  }
111
219
  }
112
220
  else {
113
- setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
221
+ if (mode === 'add-comment') {
222
+ setMode('task-detail');
223
+ }
224
+ else {
225
+ setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
226
+ }
114
227
  }
115
228
  setInputValue('');
116
229
  };
@@ -138,6 +251,14 @@ function AppContent() {
138
251
  setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }));
139
252
  await loadTasks();
140
253
  }, [i18n.tui.movedTo, i18n.status, loadTasks]);
254
+ const moveTaskToWaiting = useCallback(async (task, waitingFor) => {
255
+ const db = getDb();
256
+ await db.update(schema.tasks)
257
+ .set({ status: 'waiting', waitingFor: waitingFor.trim(), updatedAt: new Date() })
258
+ .where(eq(schema.tasks.id, task.id));
259
+ setMessage(fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }));
260
+ await loadTasks();
261
+ }, [i18n.tui.movedToWaiting, loadTasks]);
141
262
  const makeTaskProject = useCallback(async (task) => {
142
263
  const db = getDb();
143
264
  await db.update(schema.tasks)
@@ -158,6 +279,8 @@ function AppContent() {
158
279
  return i18n.tui.tabSomeday;
159
280
  case 'projects':
160
281
  return i18n.tui.tabProjects || 'Projects';
282
+ case 'done':
283
+ return i18n.tui.tabDone || 'Done';
161
284
  }
162
285
  };
163
286
  useInput((input, key) => {
@@ -166,25 +289,111 @@ function AppContent() {
166
289
  setMode('normal');
167
290
  return;
168
291
  }
169
- // Handle help mode - any key closes
292
+ // Handle help mode - let HelpModal handle input
170
293
  if (mode === 'help') {
171
- setMode('normal');
294
+ return;
295
+ }
296
+ // Handle search mode
297
+ if (mode === 'search') {
298
+ if (key.escape) {
299
+ setSearchQuery('');
300
+ setSearchResults([]);
301
+ setSearchResultIndex(0);
302
+ setMode('normal');
303
+ return;
304
+ }
305
+ // Navigate search results with Ctrl+j/k or Ctrl+n/p
306
+ if (key.ctrl && (input === 'j' || input === 'n')) {
307
+ setSearchResultIndex((prev) => prev < searchResults.length - 1 ? prev + 1 : 0);
308
+ return;
309
+ }
310
+ if (key.ctrl && (input === 'k' || input === 'p')) {
311
+ setSearchResultIndex((prev) => prev > 0 ? prev - 1 : Math.max(0, searchResults.length - 1));
312
+ return;
313
+ }
314
+ // Let TextInput handle other keys
172
315
  return;
173
316
  }
174
317
  // Handle add mode
175
- if (mode === 'add' || mode === 'add-to-project') {
318
+ if (mode === 'add' || mode === 'add-to-project' || mode === 'add-comment' || mode === 'move-to-waiting') {
176
319
  if (key.escape) {
177
320
  setInputValue('');
178
- setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
321
+ if (mode === 'move-to-waiting') {
322
+ setTaskToWaiting(null);
323
+ setMode('normal');
324
+ }
325
+ else if (mode === 'add-comment') {
326
+ setMode('task-detail');
327
+ }
328
+ else if (mode === 'add-to-project') {
329
+ setMode('project-detail');
330
+ }
331
+ else {
332
+ setMode('normal');
333
+ }
334
+ }
335
+ return;
336
+ }
337
+ // Handle task-detail mode
338
+ if (mode === 'task-detail') {
339
+ if (key.escape || key.backspace || input === 'b') {
340
+ // If came from project-detail, go back to project-detail
341
+ if (selectedProject) {
342
+ setMode('project-detail');
343
+ // Find the task index in project tasks
344
+ const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
345
+ if (taskIndex >= 0) {
346
+ setSelectedTaskIndex(taskIndex);
347
+ }
348
+ }
349
+ else {
350
+ setMode('normal');
351
+ }
352
+ setSelectedTask(null);
353
+ setTaskComments([]);
354
+ setSelectedCommentIndex(0);
355
+ return;
356
+ }
357
+ // Navigate comments
358
+ if (key.upArrow || input === 'k') {
359
+ setSelectedCommentIndex((prev) => (prev > 0 ? prev - 1 : Math.max(0, taskComments.length - 1)));
360
+ return;
361
+ }
362
+ if (key.downArrow || input === 'j') {
363
+ setSelectedCommentIndex((prev) => (prev < taskComments.length - 1 ? prev + 1 : 0));
364
+ return;
365
+ }
366
+ // Delete comment
367
+ if (input === 'd' && taskComments.length > 0) {
368
+ const comment = taskComments[selectedCommentIndex];
369
+ deleteComment(comment).then(() => {
370
+ if (selectedCommentIndex >= taskComments.length - 1) {
371
+ setSelectedCommentIndex(Math.max(0, selectedCommentIndex - 1));
372
+ }
373
+ });
374
+ return;
375
+ }
376
+ // Add comment
377
+ if (input === 'i') {
378
+ setMode('add-comment');
379
+ return;
380
+ }
381
+ // Link to project (P key)
382
+ if (input === 'P' && tasks.projects.length > 0) {
383
+ setTaskToLink(selectedTask);
384
+ setProjectSelectIndex(0);
385
+ setMode('select-project');
386
+ return;
179
387
  }
180
388
  return;
181
389
  }
182
390
  // Handle select-project mode
183
391
  if (mode === 'select-project') {
184
392
  if (key.escape) {
393
+ const wasFromTaskDetail = selectedTask !== null;
185
394
  setTaskToLink(null);
186
395
  setProjectSelectIndex(0);
187
- setMode('normal');
396
+ setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
188
397
  return;
189
398
  }
190
399
  // Navigate projects
@@ -199,10 +408,11 @@ function AppContent() {
199
408
  // Select project with Enter
200
409
  if (key.return && taskToLink && tasks.projects.length > 0) {
201
410
  const project = tasks.projects[projectSelectIndex];
411
+ const wasFromTaskDetail = selectedTask !== null;
202
412
  linkTaskToProject(taskToLink, project);
203
413
  setTaskToLink(null);
204
414
  setProjectSelectIndex(0);
205
- setMode('normal');
415
+ setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
206
416
  return;
207
417
  }
208
418
  return;
@@ -240,6 +450,14 @@ function AppContent() {
240
450
  });
241
451
  return;
242
452
  }
453
+ // Enter to view task details
454
+ if (key.return && projectTasks.length > 0) {
455
+ const task = projectTasks[selectedTaskIndex];
456
+ setSelectedTask(task);
457
+ loadTaskComments(task.id);
458
+ setMode('task-detail');
459
+ return;
460
+ }
243
461
  return;
244
462
  }
245
463
  // Clear message on any input
@@ -294,6 +512,14 @@ function AppContent() {
294
512
  setMode('help');
295
513
  return;
296
514
  }
515
+ // Search mode
516
+ if (input === '/') {
517
+ setMode('search');
518
+ setSearchQuery('');
519
+ setSearchResults([]);
520
+ setSearchResultIndex(0);
521
+ return;
522
+ }
297
523
  // Quit
298
524
  if (input === 'q' || (key.ctrl && input === 'c')) {
299
525
  exit();
@@ -330,6 +556,11 @@ function AppContent() {
330
556
  setSelectedTaskIndex(0);
331
557
  return;
332
558
  }
559
+ if (input === '6') {
560
+ setCurrentListIndex(5);
561
+ setSelectedTaskIndex(0);
562
+ return;
563
+ }
333
564
  // Navigate between lists
334
565
  if (key.leftArrow || input === 'h') {
335
566
  setCurrentListIndex((prev) => (prev > 0 ? prev - 1 : TABS.length - 1));
@@ -359,8 +590,16 @@ function AppContent() {
359
590
  setSelectedTaskIndex(0);
360
591
  return;
361
592
  }
593
+ // Enter to view task details (on non-projects tabs)
594
+ if (key.return && currentTab !== 'projects' && currentTasks.length > 0) {
595
+ const task = currentTasks[selectedTaskIndex];
596
+ setSelectedTask(task);
597
+ loadTaskComments(task.id);
598
+ setMode('task-detail');
599
+ return;
600
+ }
362
601
  // Mark as project (p key)
363
- if (input === 'p' && currentTasks.length > 0 && currentTab !== 'projects') {
602
+ if (input === 'p' && currentTasks.length > 0 && currentTab !== 'projects' && currentTab !== 'done') {
364
603
  const task = currentTasks[selectedTaskIndex];
365
604
  makeTaskProject(task).then(() => {
366
605
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -370,15 +609,15 @@ function AppContent() {
370
609
  return;
371
610
  }
372
611
  // Link to project (P key - shift+p)
373
- if (input === 'P' && currentTasks.length > 0 && currentTab !== 'projects' && tasks.projects.length > 0) {
612
+ if (input === 'P' && currentTasks.length > 0 && currentTab !== 'projects' && currentTab !== 'done' && tasks.projects.length > 0) {
374
613
  const task = currentTasks[selectedTaskIndex];
375
614
  setTaskToLink(task);
376
615
  setProjectSelectIndex(0);
377
616
  setMode('select-project');
378
617
  return;
379
618
  }
380
- // Mark as done
381
- if (input === 'd' && currentTasks.length > 0) {
619
+ // Mark as done (not available on done tab)
620
+ if (input === 'd' && currentTasks.length > 0 && currentTab !== 'done') {
382
621
  const task = currentTasks[selectedTaskIndex];
383
622
  markTaskDone(task).then(() => {
384
623
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -388,7 +627,7 @@ function AppContent() {
388
627
  return;
389
628
  }
390
629
  // Move to next actions
391
- if (input === 'n' && currentTasks.length > 0 && currentTab !== 'next' && currentTab !== 'projects') {
630
+ if (input === 'n' && currentTasks.length > 0 && currentTab !== 'next' && currentTab !== 'projects' && currentTab !== 'done') {
392
631
  const task = currentTasks[selectedTaskIndex];
393
632
  moveTaskToStatus(task, 'next').then(() => {
394
633
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -398,7 +637,7 @@ function AppContent() {
398
637
  return;
399
638
  }
400
639
  // Move to someday
401
- if (input === 's' && currentTasks.length > 0 && currentTab !== 'someday' && currentTab !== 'projects') {
640
+ if (input === 's' && currentTasks.length > 0 && currentTab !== 'someday' && currentTab !== 'projects' && currentTab !== 'done') {
402
641
  const task = currentTasks[selectedTaskIndex];
403
642
  moveTaskToStatus(task, 'someday').then(() => {
404
643
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -407,8 +646,15 @@ function AppContent() {
407
646
  });
408
647
  return;
409
648
  }
649
+ // Move to waiting
650
+ if (input === 'w' && currentTasks.length > 0 && currentTab !== 'waiting' && currentTab !== 'projects' && currentTab !== 'done') {
651
+ const task = currentTasks[selectedTaskIndex];
652
+ setTaskToWaiting(task);
653
+ setMode('move-to-waiting');
654
+ return;
655
+ }
410
656
  // Move to inbox
411
- if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects') {
657
+ if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects' && currentTab !== 'done') {
412
658
  const task = currentTasks[selectedTaskIndex];
413
659
  moveTaskToStatus(task, 'inbox').then(() => {
414
660
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -451,15 +697,24 @@ function AppContent() {
451
697
  }
452
698
  return '';
453
699
  })() : '';
454
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: formatTitle(i18n.tui.title) }), tursoEnabled && (_jsxs(Text, { color: theme.colors.accent, children: [theme.name === 'modern' ? ' ☁️ ' : ' [SYNC] ', tursoHost] })), !tursoEnabled && (_jsx(Text, { color: theme.colors.textMuted, children: theme.name === 'modern' ? ' 💾 local' : ' [LOCAL]' }))] }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.helpHint })] }), _jsx(Box, { marginBottom: 1, children: TABS.map((tab, index) => {
700
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: formatTitle(i18n.tui.title) }), _jsx(Text, { color: theme.colors.textMuted, children: theme.name === 'modern' ? ` v${VERSION}` : ` VER ${VERSION}` }), tursoEnabled && (_jsxs(Text, { color: theme.colors.accent, children: [theme.name === 'modern' ? ' ☁️ ' : ' [SYNC] ', tursoHost] })), !tursoEnabled && (_jsx(Text, { color: theme.colors.textMuted, children: theme.name === 'modern' ? ' 💾 local' : ' [LOCAL]' }))] }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.helpHint })] }), _jsx(Box, { marginBottom: 1, children: TABS.map((tab, index) => {
455
701
  const isActive = index === currentListIndex && mode !== 'project-detail';
456
702
  const count = tasks[tab].length;
457
703
  const label = `${index + 1}:${getTabLabel(tab)}(${count})`;
458
704
  return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { color: isActive ? theme.colors.textSelected : theme.colors.textMuted, bold: isActive, inverse: isActive && theme.style.tabActiveInverse, children: formatTabLabel(` ${label} `, isActive) }) }, tab));
459
- }) }), mode === 'project-detail' && selectedProject && (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] })), _jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
705
+ }) }), mode === 'project-detail' && selectedProject && (_jsxs(_Fragment, { children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.projectTasks || 'Tasks', " (", projectTasks.length, ")"] }) })] })), (mode === 'task-detail' || mode === 'add-comment') && selectedTask && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📋 ' : '>> ', i18n.tui.taskDetailTitle || 'Task Details'] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ", ", i18n.tui.commentHint || 'i: add comment', ")"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, marginBottom: 1, children: [_jsx(Text, { color: theme.colors.text, bold: true, children: selectedTask.title }), selectedTask.description && (_jsx(Text, { color: theme.colors.textMuted, children: selectedTask.description })), _jsxs(Text, { color: theme.colors.textMuted, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` - ${selectedTask.waitingFor}`] })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.comments || 'Comments', " (", taskComments.length, ")"] }) }), _jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 5, children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
706
+ const isSelected = index === selectedCommentIndex && mode === 'task-detail';
707
+ return (_jsxs(Box, { flexDirection: "row", marginBottom: 1, children: [_jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.textMuted, children: isSelected ? theme.style.selectedPrefix : theme.style.unselectedPrefix }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.textMuted, children: ["[", comment.createdAt.toLocaleString(), "]"] }), _jsx(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: comment.content })] })] }, comment.id));
708
+ })) }), mode === 'add-comment' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.addComment || 'New comment: ' }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] }))] })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), mode !== 'task-detail' && mode !== 'add-comment' && mode !== 'search' && (_jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
460
709
  const parentProject = getParentProject(task.parentId);
461
- return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title }, task.id));
462
- })) }), (mode === 'add' || mode === 'add-to-project') && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: mode === 'add-to-project' && selectedProject
710
+ const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
711
+ return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
712
+ })) })), (mode === 'add' || mode === 'add-to-project') && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: mode === 'add-to-project' && selectedProject
463
713
  ? `${i18n.tui.newTask}[${selectedProject.title}] `
464
- : i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'select-project' && taskToLink && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project for', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? theme.style.selectedPrefix : theme.style.unselectedPrefix, project.title] }, project.id))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.selectProjectHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsx(Box, { marginTop: 1, children: theme.style.showFunctionKeys ? (_jsx(FunctionKeyBar, {})) : (_jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.footer })) })] }));
714
+ : i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'move-to-waiting' && taskToWaiting && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.waitingFor }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'select-project' && taskToLink && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project for', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? theme.style.selectedPrefix : theme.style.unselectedPrefix, project.title] }, project.id))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.selectProjectHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsx(Box, { marginTop: 1, children: (mode === 'task-detail' || mode === 'add-comment') ? (theme.style.showFunctionKeys ? (_jsx(FunctionKeyBar, { keys: [
715
+ { key: 'i', label: i18n.tui.keyBar.comment },
716
+ { key: 'd', label: i18n.tui.keyBar.delete },
717
+ { key: 'P', label: i18n.tui.keyBar.project },
718
+ { key: 'b', label: i18n.tui.keyBar.back },
719
+ ] })) : (_jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.taskDetailFooter }))) : theme.style.showFunctionKeys ? (_jsx(FunctionKeyBar, {})) : (_jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.footer })) })] }));
465
720
  }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { type ViewMode } from '../config.js';
3
+ interface ModeSelectorProps {
4
+ onSelect: (mode: ViewMode) => void;
5
+ }
6
+ export declare function ModeSelector({ onSelect }: ModeSelectorProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { getViewMode } from '../config.js';
5
+ const VALID_VIEW_MODES = ['gtd', 'kanban'];
6
+ const modeDisplayNames = {
7
+ gtd: 'GTD (Getting Things Done)',
8
+ kanban: 'Kanban Board',
9
+ };
10
+ const modeDescriptions = {
11
+ gtd: 'Classic GTD workflow with Inbox, Next, Waiting, Someday lists',
12
+ kanban: '3-column kanban board view',
13
+ };
14
+ export function ModeSelector({ onSelect }) {
15
+ const currentMode = getViewMode();
16
+ const initialIndex = VALID_VIEW_MODES.indexOf(currentMode);
17
+ const [selectedIndex, setSelectedIndex] = useState(initialIndex >= 0 ? initialIndex : 0);
18
+ useInput((input, key) => {
19
+ // j or down arrow: move down
20
+ if (input === 'j' || key.downArrow) {
21
+ setSelectedIndex((prev) => (prev < VALID_VIEW_MODES.length - 1 ? prev + 1 : 0));
22
+ }
23
+ // k or up arrow: move up
24
+ if (input === 'k' || key.upArrow) {
25
+ setSelectedIndex((prev) => (prev > 0 ? prev - 1 : VALID_VIEW_MODES.length - 1));
26
+ }
27
+ // Enter: select
28
+ if (key.return) {
29
+ onSelect(VALID_VIEW_MODES[selectedIndex]);
30
+ }
31
+ });
32
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Select a view mode:" }), _jsx(Text, { dimColor: true, children: "j/k: select, Enter: confirm" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VALID_VIEW_MODES.map((mode, index) => {
33
+ const isSelected = index === selectedIndex;
34
+ const isCurrent = mode === currentMode;
35
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [isSelected ? '› ' : ' ', modeDisplayNames[mode], isCurrent ? ' (current)' : ''] }), _jsxs(Text, { dimColor: true, children: [' ', modeDescriptions[mode]] })] }, mode));
36
+ }) })] }));
37
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface SetupWizardProps {
3
+ onComplete: () => void;
4
+ }
5
+ export declare function SetupWizard({ onComplete }: SetupWizardProps): React.ReactElement;
6
+ export {};