floq 0.1.0 → 0.2.1

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 +278 -0
  18. package/dist/i18n/en.js +148 -3
  19. package/dist/i18n/ja.js +148 -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 +320 -37
  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 +155 -4
  30. package/dist/ui/components/KanbanBoard.d.ts +6 -0
  31. package/dist/ui/components/KanbanBoard.js +498 -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 } 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,23 @@ 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 [taskToDelete, setTaskToDelete] = useState(null);
50
+ const [projectProgress, setProjectProgress] = useState({});
51
+ // Search state
52
+ const [searchQuery, setSearchQuery] = useState('');
53
+ const [searchResults, setSearchResults] = useState([]);
54
+ const [searchResultIndex, setSearchResultIndex] = useState(0);
39
55
  const i18n = t();
40
56
  const loadTasks = useCallback(async () => {
41
57
  const db = getDb();
@@ -45,9 +61,10 @@ function AppContent() {
45
61
  waiting: [],
46
62
  someday: [],
47
63
  projects: [],
64
+ done: [],
48
65
  };
49
66
  // Load all tasks (including project children) by status
50
- const statusList = ['inbox', 'next', 'waiting', 'someday'];
67
+ const statusList = ['inbox', 'next', 'waiting', 'someday', 'done'];
51
68
  for (const status of statusList) {
52
69
  newTasks[status] = await db
53
70
  .select()
@@ -59,6 +76,18 @@ function AppContent() {
59
76
  .select()
60
77
  .from(schema.tasks)
61
78
  .where(and(eq(schema.tasks.isProject, true), eq(schema.tasks.status, 'next')));
79
+ // Calculate progress for each project
80
+ const progress = {};
81
+ for (const project of newTasks.projects) {
82
+ const children = await db
83
+ .select()
84
+ .from(schema.tasks)
85
+ .where(eq(schema.tasks.parentId, project.id));
86
+ const total = children.length;
87
+ const completed = children.filter(t => t.status === 'done').length;
88
+ progress[project.id] = { completed, total };
89
+ }
90
+ setProjectProgress(progress);
62
91
  setTasks(newTasks);
63
92
  }, []);
64
93
  // Get parent project for a task
@@ -75,11 +104,43 @@ function AppContent() {
75
104
  .where(eq(schema.tasks.parentId, projectId));
76
105
  setProjectTasks(children);
77
106
  }, []);
107
+ const loadTaskComments = useCallback(async (taskId) => {
108
+ const db = getDb();
109
+ const comments = await db
110
+ .select()
111
+ .from(schema.comments)
112
+ .where(eq(schema.comments.taskId, taskId));
113
+ setTaskComments(comments);
114
+ }, []);
78
115
  useEffect(() => {
79
116
  loadTasks();
80
117
  }, [loadTasks]);
81
118
  const currentTab = TABS[currentListIndex];
82
119
  const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
120
+ // Get all tasks for search (across all statuses)
121
+ const getAllTasks = useCallback(() => {
122
+ const allTasks = [];
123
+ for (const tab of TABS) {
124
+ allTasks.push(...tasks[tab]);
125
+ }
126
+ return allTasks;
127
+ }, [tasks]);
128
+ // Search tasks by query
129
+ const searchTasks = useCallback((query) => {
130
+ if (!query.trim())
131
+ return [];
132
+ const lowerQuery = query.toLowerCase();
133
+ const allTasks = getAllTasks();
134
+ return allTasks.filter(task => task.title.toLowerCase().includes(lowerQuery) ||
135
+ (task.description && task.description.toLowerCase().includes(lowerQuery)));
136
+ }, [getAllTasks]);
137
+ // Handle search query change
138
+ const handleSearchChange = useCallback((value) => {
139
+ setSearchQuery(value);
140
+ const results = searchTasks(value);
141
+ setSearchResults(results);
142
+ setSearchResultIndex(0);
143
+ }, [searchTasks]);
83
144
  const addTask = useCallback(async (title, parentId) => {
84
145
  if (!title.trim())
85
146
  return;
@@ -97,9 +158,57 @@ function AppContent() {
97
158
  setMessage(fmt(i18n.tui.added, { title: title.trim() }));
98
159
  await loadTasks();
99
160
  }, [i18n.tui.added, loadTasks]);
161
+ const addCommentToTask = useCallback(async (task, content) => {
162
+ const db = getDb();
163
+ await db.insert(schema.comments).values({
164
+ id: uuidv4(),
165
+ taskId: task.id,
166
+ content: content.trim(),
167
+ createdAt: new Date(),
168
+ });
169
+ setMessage(i18n.tui.commentAdded || 'Comment added');
170
+ await loadTaskComments(task.id);
171
+ }, [i18n.tui.commentAdded, loadTaskComments]);
172
+ const deleteComment = useCallback(async (comment) => {
173
+ const db = getDb();
174
+ await db.delete(schema.comments).where(eq(schema.comments.id, comment.id));
175
+ setMessage(i18n.tui.commentDeleted || 'Comment deleted');
176
+ if (selectedTask) {
177
+ await loadTaskComments(selectedTask.id);
178
+ }
179
+ }, [i18n.tui.commentDeleted, loadTaskComments, selectedTask]);
100
180
  const handleInputSubmit = async (value) => {
181
+ if (mode === 'move-to-waiting' && taskToWaiting) {
182
+ if (value.trim()) {
183
+ await moveTaskToWaiting(taskToWaiting, value);
184
+ }
185
+ setTaskToWaiting(null);
186
+ setMode('normal');
187
+ setInputValue('');
188
+ return;
189
+ }
190
+ // Handle search mode submit
191
+ if (mode === 'search') {
192
+ if (searchResults.length > 0) {
193
+ const task = searchResults[searchResultIndex];
194
+ setSelectedTask(task);
195
+ loadTaskComments(task.id);
196
+ setMode('task-detail');
197
+ }
198
+ else {
199
+ setMode('normal');
200
+ }
201
+ setSearchQuery('');
202
+ setSearchResults([]);
203
+ setSearchResultIndex(0);
204
+ return;
205
+ }
101
206
  if (value.trim()) {
102
- if (mode === 'add-to-project' && selectedProject) {
207
+ if (mode === 'add-comment' && selectedTask) {
208
+ await addCommentToTask(selectedTask, value);
209
+ setMode('task-detail');
210
+ }
211
+ else if (mode === 'add-to-project' && selectedProject) {
103
212
  await addTask(value, selectedProject.id);
104
213
  await loadProjectTasks(selectedProject.id);
105
214
  setMode('project-detail');
@@ -110,7 +219,12 @@ function AppContent() {
110
219
  }
111
220
  }
112
221
  else {
113
- setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
222
+ if (mode === 'add-comment') {
223
+ setMode('task-detail');
224
+ }
225
+ else {
226
+ setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
227
+ }
114
228
  }
115
229
  setInputValue('');
116
230
  };
@@ -138,6 +252,14 @@ function AppContent() {
138
252
  setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }));
139
253
  await loadTasks();
140
254
  }, [i18n.tui.movedTo, i18n.status, loadTasks]);
255
+ const moveTaskToWaiting = useCallback(async (task, waitingFor) => {
256
+ const db = getDb();
257
+ await db.update(schema.tasks)
258
+ .set({ status: 'waiting', waitingFor: waitingFor.trim(), updatedAt: new Date() })
259
+ .where(eq(schema.tasks.id, task.id));
260
+ setMessage(fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }));
261
+ await loadTasks();
262
+ }, [i18n.tui.movedToWaiting, loadTasks]);
141
263
  const makeTaskProject = useCallback(async (task) => {
142
264
  const db = getDb();
143
265
  await db.update(schema.tasks)
@@ -146,6 +268,15 @@ function AppContent() {
146
268
  setMessage(fmt(i18n.tui.madeProject || 'Made project: {title}', { title: task.title }));
147
269
  await loadTasks();
148
270
  }, [i18n.tui.madeProject, loadTasks]);
271
+ const deleteTask = useCallback(async (task) => {
272
+ const db = getDb();
273
+ // Delete comments first
274
+ await db.delete(schema.comments).where(eq(schema.comments.taskId, task.id));
275
+ // Delete the task
276
+ await db.delete(schema.tasks).where(eq(schema.tasks.id, task.id));
277
+ setMessage(fmt(i18n.tui.deleted || 'Deleted: "{title}"', { title: task.title }));
278
+ await loadTasks();
279
+ }, [i18n.tui.deleted, loadTasks]);
149
280
  const getTabLabel = (tab) => {
150
281
  switch (tab) {
151
282
  case 'inbox':
@@ -158,6 +289,8 @@ function AppContent() {
158
289
  return i18n.tui.tabSomeday;
159
290
  case 'projects':
160
291
  return i18n.tui.tabProjects || 'Projects';
292
+ case 'done':
293
+ return i18n.tui.tabDone || 'Done';
161
294
  }
162
295
  };
163
296
  useInput((input, key) => {
@@ -166,25 +299,132 @@ function AppContent() {
166
299
  setMode('normal');
167
300
  return;
168
301
  }
169
- // Handle help mode - any key closes
302
+ // Handle help mode - let HelpModal handle input
170
303
  if (mode === 'help') {
171
- setMode('normal');
304
+ return;
305
+ }
306
+ // Handle search mode
307
+ if (mode === 'search') {
308
+ if (key.escape) {
309
+ setSearchQuery('');
310
+ setSearchResults([]);
311
+ setSearchResultIndex(0);
312
+ setMode('normal');
313
+ return;
314
+ }
315
+ // Navigate search results with Ctrl+j/k or Ctrl+n/p
316
+ if (key.ctrl && (input === 'j' || input === 'n')) {
317
+ setSearchResultIndex((prev) => prev < searchResults.length - 1 ? prev + 1 : 0);
318
+ return;
319
+ }
320
+ if (key.ctrl && (input === 'k' || input === 'p')) {
321
+ setSearchResultIndex((prev) => prev > 0 ? prev - 1 : Math.max(0, searchResults.length - 1));
322
+ return;
323
+ }
324
+ // Let TextInput handle other keys
325
+ return;
326
+ }
327
+ // Handle confirm-delete mode
328
+ if (mode === 'confirm-delete' && taskToDelete) {
329
+ if (input === 'y' || input === 'Y') {
330
+ deleteTask(taskToDelete).then(() => {
331
+ if (selectedTaskIndex >= currentTasks.length - 1) {
332
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
333
+ }
334
+ });
335
+ setTaskToDelete(null);
336
+ setMode('normal');
337
+ return;
338
+ }
339
+ if (input === 'n' || input === 'N' || key.escape) {
340
+ setMessage(i18n.tui.deleteCancelled || 'Delete cancelled');
341
+ setTaskToDelete(null);
342
+ setMode('normal');
343
+ return;
344
+ }
345
+ // Ignore other keys in confirm mode
172
346
  return;
173
347
  }
174
348
  // Handle add mode
175
- if (mode === 'add' || mode === 'add-to-project') {
349
+ if (mode === 'add' || mode === 'add-to-project' || mode === 'add-comment' || mode === 'move-to-waiting') {
176
350
  if (key.escape) {
177
351
  setInputValue('');
178
- setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
352
+ if (mode === 'move-to-waiting') {
353
+ setTaskToWaiting(null);
354
+ setMode('normal');
355
+ }
356
+ else if (mode === 'add-comment') {
357
+ setMode('task-detail');
358
+ }
359
+ else if (mode === 'add-to-project') {
360
+ setMode('project-detail');
361
+ }
362
+ else {
363
+ setMode('normal');
364
+ }
365
+ }
366
+ return;
367
+ }
368
+ // Handle task-detail mode
369
+ if (mode === 'task-detail') {
370
+ if (key.escape || key.backspace || input === 'b') {
371
+ // If came from project-detail, go back to project-detail
372
+ if (selectedProject) {
373
+ setMode('project-detail');
374
+ // Find the task index in project tasks
375
+ const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
376
+ if (taskIndex >= 0) {
377
+ setSelectedTaskIndex(taskIndex);
378
+ }
379
+ }
380
+ else {
381
+ setMode('normal');
382
+ }
383
+ setSelectedTask(null);
384
+ setTaskComments([]);
385
+ setSelectedCommentIndex(0);
386
+ return;
387
+ }
388
+ // Navigate comments
389
+ if (key.upArrow || input === 'k') {
390
+ setSelectedCommentIndex((prev) => (prev > 0 ? prev - 1 : Math.max(0, taskComments.length - 1)));
391
+ return;
392
+ }
393
+ if (key.downArrow || input === 'j') {
394
+ setSelectedCommentIndex((prev) => (prev < taskComments.length - 1 ? prev + 1 : 0));
395
+ return;
396
+ }
397
+ // Delete comment
398
+ if (input === 'd' && taskComments.length > 0) {
399
+ const comment = taskComments[selectedCommentIndex];
400
+ deleteComment(comment).then(() => {
401
+ if (selectedCommentIndex >= taskComments.length - 1) {
402
+ setSelectedCommentIndex(Math.max(0, selectedCommentIndex - 1));
403
+ }
404
+ });
405
+ return;
406
+ }
407
+ // Add comment
408
+ if (input === 'i') {
409
+ setMode('add-comment');
410
+ return;
411
+ }
412
+ // Link to project (P key)
413
+ if (input === 'P' && tasks.projects.length > 0) {
414
+ setTaskToLink(selectedTask);
415
+ setProjectSelectIndex(0);
416
+ setMode('select-project');
417
+ return;
179
418
  }
180
419
  return;
181
420
  }
182
421
  // Handle select-project mode
183
422
  if (mode === 'select-project') {
184
423
  if (key.escape) {
424
+ const wasFromTaskDetail = selectedTask !== null;
185
425
  setTaskToLink(null);
186
426
  setProjectSelectIndex(0);
187
- setMode('normal');
427
+ setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
188
428
  return;
189
429
  }
190
430
  // Navigate projects
@@ -199,10 +439,11 @@ function AppContent() {
199
439
  // Select project with Enter
200
440
  if (key.return && taskToLink && tasks.projects.length > 0) {
201
441
  const project = tasks.projects[projectSelectIndex];
442
+ const wasFromTaskDetail = selectedTask !== null;
202
443
  linkTaskToProject(taskToLink, project);
203
444
  setTaskToLink(null);
204
445
  setProjectSelectIndex(0);
205
- setMode('normal');
446
+ setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
206
447
  return;
207
448
  }
208
449
  return;
@@ -240,6 +481,14 @@ function AppContent() {
240
481
  });
241
482
  return;
242
483
  }
484
+ // Enter to view task details
485
+ if (key.return && projectTasks.length > 0) {
486
+ const task = projectTasks[selectedTaskIndex];
487
+ setSelectedTask(task);
488
+ loadTaskComments(task.id);
489
+ setMode('task-detail');
490
+ return;
491
+ }
243
492
  return;
244
493
  }
245
494
  // Clear message on any input
@@ -294,6 +543,14 @@ function AppContent() {
294
543
  setMode('help');
295
544
  return;
296
545
  }
546
+ // Search mode
547
+ if (input === '/') {
548
+ setMode('search');
549
+ setSearchQuery('');
550
+ setSearchResults([]);
551
+ setSearchResultIndex(0);
552
+ return;
553
+ }
297
554
  // Quit
298
555
  if (input === 'q' || (key.ctrl && input === 'c')) {
299
556
  exit();
@@ -330,6 +587,11 @@ function AppContent() {
330
587
  setSelectedTaskIndex(0);
331
588
  return;
332
589
  }
590
+ if (input === '6') {
591
+ setCurrentListIndex(5);
592
+ setSelectedTaskIndex(0);
593
+ return;
594
+ }
333
595
  // Navigate between lists
334
596
  if (key.leftArrow || input === 'h') {
335
597
  setCurrentListIndex((prev) => (prev > 0 ? prev - 1 : TABS.length - 1));
@@ -359,8 +621,16 @@ function AppContent() {
359
621
  setSelectedTaskIndex(0);
360
622
  return;
361
623
  }
624
+ // Enter to view task details (on non-projects tabs)
625
+ if (key.return && currentTab !== 'projects' && currentTasks.length > 0) {
626
+ const task = currentTasks[selectedTaskIndex];
627
+ setSelectedTask(task);
628
+ loadTaskComments(task.id);
629
+ setMode('task-detail');
630
+ return;
631
+ }
362
632
  // Mark as project (p key)
363
- if (input === 'p' && currentTasks.length > 0 && currentTab !== 'projects') {
633
+ if (input === 'p' && currentTasks.length > 0 && currentTab !== 'projects' && currentTab !== 'done') {
364
634
  const task = currentTasks[selectedTaskIndex];
365
635
  makeTaskProject(task).then(() => {
366
636
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -370,15 +640,15 @@ function AppContent() {
370
640
  return;
371
641
  }
372
642
  // Link to project (P key - shift+p)
373
- if (input === 'P' && currentTasks.length > 0 && currentTab !== 'projects' && tasks.projects.length > 0) {
643
+ if (input === 'P' && currentTasks.length > 0 && currentTab !== 'projects' && currentTab !== 'done' && tasks.projects.length > 0) {
374
644
  const task = currentTasks[selectedTaskIndex];
375
645
  setTaskToLink(task);
376
646
  setProjectSelectIndex(0);
377
647
  setMode('select-project');
378
648
  return;
379
649
  }
380
- // Mark as done
381
- if (input === 'd' && currentTasks.length > 0) {
650
+ // Mark as done (not available on done tab)
651
+ if (input === 'd' && currentTasks.length > 0 && currentTab !== 'done') {
382
652
  const task = currentTasks[selectedTaskIndex];
383
653
  markTaskDone(task).then(() => {
384
654
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -387,8 +657,15 @@ function AppContent() {
387
657
  });
388
658
  return;
389
659
  }
660
+ // Delete task (D key - with confirmation)
661
+ if (input === 'D' && currentTasks.length > 0 && currentTab !== 'projects') {
662
+ const task = currentTasks[selectedTaskIndex];
663
+ setTaskToDelete(task);
664
+ setMode('confirm-delete');
665
+ return;
666
+ }
390
667
  // Move to next actions
391
- if (input === 'n' && currentTasks.length > 0 && currentTab !== 'next' && currentTab !== 'projects') {
668
+ if (input === 'n' && currentTasks.length > 0 && currentTab !== 'next' && currentTab !== 'projects' && currentTab !== 'done') {
392
669
  const task = currentTasks[selectedTaskIndex];
393
670
  moveTaskToStatus(task, 'next').then(() => {
394
671
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -398,7 +675,7 @@ function AppContent() {
398
675
  return;
399
676
  }
400
677
  // Move to someday
401
- if (input === 's' && currentTasks.length > 0 && currentTab !== 'someday' && currentTab !== 'projects') {
678
+ if (input === 's' && currentTasks.length > 0 && currentTab !== 'someday' && currentTab !== 'projects' && currentTab !== 'done') {
402
679
  const task = currentTasks[selectedTaskIndex];
403
680
  moveTaskToStatus(task, 'someday').then(() => {
404
681
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -407,8 +684,15 @@ function AppContent() {
407
684
  });
408
685
  return;
409
686
  }
687
+ // Move to waiting
688
+ if (input === 'w' && currentTasks.length > 0 && currentTab !== 'waiting' && currentTab !== 'projects' && currentTab !== 'done') {
689
+ const task = currentTasks[selectedTaskIndex];
690
+ setTaskToWaiting(task);
691
+ setMode('move-to-waiting');
692
+ return;
693
+ }
410
694
  // Move to inbox
411
- if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects') {
695
+ if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects' && currentTab !== 'done') {
412
696
  const task = currentTasks[selectedTaskIndex];
413
697
  moveTaskToStatus(task, 'inbox').then(() => {
414
698
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -439,27 +723,26 @@ function AppContent() {
439
723
  };
440
724
  // Turso 接続情報を取得
441
725
  const tursoEnabled = isTursoEnabled();
442
- const tursoHost = tursoEnabled ? (() => {
443
- const config = getTursoConfig();
444
- if (config) {
445
- try {
446
- return new URL(config.url).host;
447
- }
448
- catch {
449
- return config.url;
450
- }
451
- }
452
- return '';
453
- })() : '';
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) => {
726
+ 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}` }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: theme.name === 'modern'
727
+ ? (tursoEnabled ? ' ☁️ turso' : ' 💾 local')
728
+ : (tursoEnabled ? ' [DB]turso' : ' [DB]local') })] }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.helpHint })] }), _jsx(Box, { marginBottom: 1, children: TABS.map((tab, index) => {
455
729
  const isActive = index === currentListIndex && mode !== 'project-detail';
456
730
  const count = tasks[tab].length;
457
731
  const label = `${index + 1}:${getTabLabel(tab)}(${count})`;
458
732
  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) => {
733
+ }) }), 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(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus, ": "] }), _jsxs(Text, { color: theme.colors.accent, 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) => {
734
+ const isSelected = index === selectedCommentIndex && mode === 'task-detail';
735
+ 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));
736
+ })) }), 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
737
  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
738
+ const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
739
+ return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
740
+ })) })), (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
741
  ? `${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 })) })] }));
742
+ : 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 })), mode === 'confirm-delete' && taskToDelete && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })), 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: [
743
+ { key: 'i', label: i18n.tui.keyBar.comment },
744
+ { key: 'd', label: i18n.tui.keyBar.delete },
745
+ { key: 'P', label: i18n.tui.keyBar.project },
746
+ { key: 'b', label: i18n.tui.keyBar.back },
747
+ ] })) : (_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
748
  }
@@ -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 {};