floq 1.4.0 → 1.5.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 (49) hide show
  1. package/dist/commands/done.js +1 -0
  2. package/dist/commands/insights.js +13 -10
  3. package/dist/commands/move.js +2 -1
  4. package/dist/db/index.js +36 -0
  5. package/dist/db/schema.d.ts +116 -0
  6. package/dist/db/schema.js +8 -0
  7. package/dist/i18n/en.d.ts +2 -0
  8. package/dist/i18n/en.js +1 -0
  9. package/dist/i18n/ja.js +1 -0
  10. package/dist/ui/App.js +7 -4
  11. package/dist/ui/components/GtdDQ.js +27 -5
  12. package/dist/ui/components/GtdMario.js +27 -5
  13. package/dist/ui/components/InsightsModal.js +13 -7
  14. package/dist/ui/components/KanbanBoard.js +3 -0
  15. package/dist/ui/components/KanbanDQ.js +2 -0
  16. package/dist/ui/components/KanbanMario.js +2 -0
  17. package/dist/ui/components/TaskItem.d.ts +2 -1
  18. package/dist/ui/components/TaskItem.js +4 -3
  19. package/dist/ui/history/HistoryContext.js +8 -0
  20. package/dist/ui/history/HistoryManager.d.ts +9 -1
  21. package/dist/ui/history/HistoryManager.js +140 -16
  22. package/dist/ui/history/commands/ConvertToProjectCommand.d.ts +5 -1
  23. package/dist/ui/history/commands/ConvertToProjectCommand.js +13 -0
  24. package/dist/ui/history/commands/CreateCommentCommand.d.ts +5 -1
  25. package/dist/ui/history/commands/CreateCommentCommand.js +22 -0
  26. package/dist/ui/history/commands/CreateTaskCommand.d.ts +5 -1
  27. package/dist/ui/history/commands/CreateTaskCommand.js +26 -1
  28. package/dist/ui/history/commands/DeleteCommentCommand.d.ts +5 -1
  29. package/dist/ui/history/commands/DeleteCommentCommand.js +22 -0
  30. package/dist/ui/history/commands/DeleteTaskCommand.d.ts +7 -2
  31. package/dist/ui/history/commands/DeleteTaskCommand.js +41 -0
  32. package/dist/ui/history/commands/LinkTaskCommand.d.ts +5 -1
  33. package/dist/ui/history/commands/LinkTaskCommand.js +14 -0
  34. package/dist/ui/history/commands/MoveTaskCommand.d.ts +7 -1
  35. package/dist/ui/history/commands/MoveTaskCommand.js +25 -0
  36. package/dist/ui/history/commands/SetContextCommand.d.ts +5 -1
  37. package/dist/ui/history/commands/SetContextCommand.js +14 -0
  38. package/dist/ui/history/commands/SetEffortCommand.d.ts +5 -1
  39. package/dist/ui/history/commands/SetEffortCommand.js +14 -0
  40. package/dist/ui/history/commands/SetFocusCommand.d.ts +5 -1
  41. package/dist/ui/history/commands/SetFocusCommand.js +14 -0
  42. package/dist/ui/history/commands/index.d.ts +1 -0
  43. package/dist/ui/history/commands/index.js +1 -0
  44. package/dist/ui/history/commands/registry.d.ts +2 -0
  45. package/dist/ui/history/commands/registry.js +28 -0
  46. package/dist/ui/history/index.d.ts +2 -2
  47. package/dist/ui/history/index.js +1 -1
  48. package/dist/ui/history/types.d.ts +9 -0
  49. package/package.json +1 -1
@@ -28,6 +28,7 @@ export async function markDone(taskId) {
28
28
  await db.update(schema.tasks)
29
29
  .set({
30
30
  status: 'done',
31
+ completedAt: new Date(),
31
32
  updatedAt: new Date(),
32
33
  })
33
34
  .where(eq(schema.tasks.id, task.id));
@@ -1,4 +1,4 @@
1
- import { eq, and, gte } from 'drizzle-orm';
1
+ import { eq, and } from 'drizzle-orm';
2
2
  import { getDb, schema } from '../db/index.js';
3
3
  import { t, fmt } from '../i18n/index.js';
4
4
  function getWeekStart(date) {
@@ -53,8 +53,8 @@ function groupByWeek(tasks, weeks) {
53
53
  const weekEnd = new Date(weekStart);
54
54
  weekEnd.setDate(weekEnd.getDate() + 7);
55
55
  const weekTasks = tasks.filter(task => {
56
- const updated = task.updatedAt;
57
- return updated >= weekStart && updated < weekEnd;
56
+ const completionDate = task.completedAt ?? task.updatedAt;
57
+ return completionDate >= weekStart && completionDate < weekEnd;
58
58
  });
59
59
  result.push({
60
60
  weekStart,
@@ -69,7 +69,7 @@ function groupByDayOfWeek(tasks) {
69
69
  for (let i = 0; i < 7; i++)
70
70
  days.set(i, 0);
71
71
  for (const task of tasks) {
72
- const day = task.updatedAt.getDay();
72
+ const day = (task.completedAt ?? task.updatedAt).getDay();
73
73
  days.set(day, (days.get(day) || 0) + 1);
74
74
  }
75
75
  return days;
@@ -95,9 +95,9 @@ function calculateAverageCompletionDays(tasks) {
95
95
  let validCount = 0;
96
96
  for (const task of tasks) {
97
97
  const created = task.createdAt.getTime();
98
- const updated = task.updatedAt.getTime();
99
- if (updated > created) {
100
- totalMs += updated - created;
98
+ const completed = (task.completedAt ?? task.updatedAt).getTime();
99
+ if (completed > created) {
100
+ totalMs += completed - created;
101
101
  validCount++;
102
102
  }
103
103
  }
@@ -138,11 +138,14 @@ export async function showInsights(weeks) {
138
138
  startDate.setDate(startDate.getDate() - (weeks - 1) * 7);
139
139
  const endDate = new Date(now);
140
140
  endDate.setHours(23, 59, 59, 999);
141
- // Query all completed tasks in the period
142
- const completedTasks = await db
141
+ // Query completed tasks (use completedAt with updatedAt fallback)
142
+ const completedTasks = (await db
143
143
  .select()
144
144
  .from(schema.tasks)
145
- .where(and(eq(schema.tasks.status, 'done'), gte(schema.tasks.updatedAt, startDate), eq(schema.tasks.isProject, false)));
145
+ .where(and(eq(schema.tasks.status, 'done'), eq(schema.tasks.isProject, false)))).filter(task => {
146
+ const completionDate = task.completedAt ?? task.updatedAt;
147
+ return completionDate >= startDate;
148
+ });
146
149
  // Header
147
150
  console.log();
148
151
  console.log(l.title);
@@ -4,7 +4,7 @@ import { t, fmt } from '../i18n/index.js';
4
4
  export async function moveTask(taskId, targetStatus, waitingFor) {
5
5
  const db = getDb();
6
6
  const i18n = t();
7
- const validStatuses = ['inbox', 'next', 'waiting', 'someday'];
7
+ const validStatuses = ['inbox', 'next', 'waiting', 'someday', 'done'];
8
8
  if (!validStatuses.includes(targetStatus)) {
9
9
  console.error(fmt(i18n.commands.move.invalidStatus, { status: targetStatus }));
10
10
  console.error(fmt(i18n.commands.move.validStatuses, { statuses: validStatuses.join(', ') }));
@@ -35,6 +35,7 @@ export async function moveTask(taskId, targetStatus, waitingFor) {
35
35
  .set({
36
36
  status: targetStatus,
37
37
  waitingFor: targetStatus === 'waiting' ? waitingFor : null,
38
+ completedAt: targetStatus === 'done' ? new Date() : null,
38
39
  updatedAt: new Date(),
39
40
  })
40
41
  .where(eq(schema.tasks.id, task.id));
package/dist/db/index.js CHANGED
@@ -26,6 +26,7 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
26
26
  const hasContext = tableInfo.some(col => col.name === 'context');
27
27
  const hasIsFocused = tableInfo.some(col => col.name === 'is_focused');
28
28
  const hasEffort = tableInfo.some(col => col.name === 'effort');
29
+ const hasCompletedAt = tableInfo.some(col => col.name === 'completed_at');
29
30
  if (!tableExists) {
30
31
  // Fresh install: create new schema on remote
31
32
  await remoteClient.execute(`
@@ -41,6 +42,7 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
41
42
  is_focused INTEGER NOT NULL DEFAULT 0,
42
43
  effort TEXT,
43
44
  due_date INTEGER,
45
+ completed_at INTEGER,
44
46
  created_at INTEGER NOT NULL,
45
47
  updated_at INTEGER NOT NULL
46
48
  )
@@ -63,6 +65,11 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
63
65
  if (tableExists && !hasEffort) {
64
66
  await remoteClient.execute("ALTER TABLE tasks ADD COLUMN effort TEXT");
65
67
  }
68
+ // Migration: add completed_at column if missing
69
+ if (tableExists && !hasCompletedAt) {
70
+ await remoteClient.execute("ALTER TABLE tasks ADD COLUMN completed_at INTEGER");
71
+ await remoteClient.execute("UPDATE tasks SET completed_at = updated_at WHERE status = 'done'");
72
+ }
66
73
  // Create comments table
67
74
  await remoteClient.execute(`
68
75
  CREATE TABLE IF NOT EXISTS comments (
@@ -86,6 +93,17 @@ async function initializeRemoteSchema(tursoUrl, authToken) {
86
93
  updated_at INTEGER NOT NULL
87
94
  )
88
95
  `);
96
+ // Create operation_history table for crash-safe undo
97
+ await remoteClient.execute(`
98
+ CREATE TABLE IF NOT EXISTS operation_history (
99
+ id TEXT PRIMARY KEY,
100
+ command_type TEXT NOT NULL,
101
+ command_data TEXT NOT NULL,
102
+ executed_at INTEGER NOT NULL,
103
+ is_undone INTEGER NOT NULL DEFAULT 0
104
+ )
105
+ `);
106
+ await remoteClient.execute("CREATE INDEX IF NOT EXISTS idx_operation_history_executed_at ON operation_history(executed_at)");
89
107
  }
90
108
  finally {
91
109
  remoteClient.close();
@@ -102,6 +120,7 @@ async function initializeLocalSchema() {
102
120
  const hasContext = tableInfo.some(col => col.name === 'context');
103
121
  const hasIsFocused = tableInfo.some(col => col.name === 'is_focused');
104
122
  const hasEffort = tableInfo.some(col => col.name === 'effort');
123
+ const hasCompletedAt = tableInfo.some(col => col.name === 'completed_at');
105
124
  const tableExists = tableInfo.length > 0;
106
125
  if (tableExists && hasProjectId && !hasIsProject) {
107
126
  // Migration: old schema -> new schema
@@ -136,6 +155,7 @@ async function initializeLocalSchema() {
136
155
  is_focused INTEGER NOT NULL DEFAULT 0,
137
156
  effort TEXT,
138
157
  due_date INTEGER,
158
+ completed_at INTEGER,
139
159
  created_at INTEGER NOT NULL,
140
160
  updated_at INTEGER NOT NULL
141
161
  )
@@ -158,6 +178,11 @@ async function initializeLocalSchema() {
158
178
  if (tableExists && !hasEffort) {
159
179
  await client.execute("ALTER TABLE tasks ADD COLUMN effort TEXT");
160
180
  }
181
+ // Migration: add completed_at column if missing
182
+ if (tableExists && !hasCompletedAt) {
183
+ await client.execute("ALTER TABLE tasks ADD COLUMN completed_at INTEGER");
184
+ await client.execute("UPDATE tasks SET completed_at = updated_at WHERE status = 'done'");
185
+ }
161
186
  // Create comments table
162
187
  await client.execute(`
163
188
  CREATE TABLE IF NOT EXISTS comments (
@@ -181,6 +206,17 @@ async function initializeLocalSchema() {
181
206
  updated_at INTEGER NOT NULL
182
207
  )
183
208
  `);
209
+ // Create operation_history table for crash-safe undo
210
+ await client.execute(`
211
+ CREATE TABLE IF NOT EXISTS operation_history (
212
+ id TEXT PRIMARY KEY,
213
+ command_type TEXT NOT NULL,
214
+ command_data TEXT NOT NULL,
215
+ executed_at INTEGER NOT NULL,
216
+ is_undone INTEGER NOT NULL DEFAULT 0
217
+ )
218
+ `);
219
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_operation_history_executed_at ON operation_history(executed_at)");
184
220
  }
185
221
  // DB 初期化
186
222
  export async function initDb() {
@@ -205,6 +205,23 @@ export declare const tasks: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
205
205
  identity: undefined;
206
206
  generated: undefined;
207
207
  }, {}, {}>;
208
+ completedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
209
+ name: "completed_at";
210
+ tableName: "tasks";
211
+ dataType: "date";
212
+ columnType: "SQLiteTimestamp";
213
+ data: Date;
214
+ driverParam: number;
215
+ notNull: false;
216
+ hasDefault: false;
217
+ isPrimaryKey: false;
218
+ isAutoincrement: false;
219
+ hasRuntimeDefault: false;
220
+ enumValues: undefined;
221
+ baseColumn: never;
222
+ identity: undefined;
223
+ generated: undefined;
224
+ }, {}, {}>;
208
225
  createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
209
226
  name: "created_at";
210
227
  tableName: "tasks";
@@ -482,3 +499,102 @@ export declare const pomodoroSessions: import("drizzle-orm/sqlite-core").SQLiteT
482
499
  }>;
483
500
  export type PomodoroSession = typeof pomodoroSessions.$inferSelect;
484
501
  export type NewPomodoroSession = typeof pomodoroSessions.$inferInsert;
502
+ export declare const operationHistory: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
503
+ name: "operation_history";
504
+ schema: undefined;
505
+ columns: {
506
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
507
+ name: "id";
508
+ tableName: "operation_history";
509
+ dataType: "string";
510
+ columnType: "SQLiteText";
511
+ data: string;
512
+ driverParam: string;
513
+ notNull: true;
514
+ hasDefault: false;
515
+ isPrimaryKey: true;
516
+ isAutoincrement: false;
517
+ hasRuntimeDefault: false;
518
+ enumValues: [string, ...string[]];
519
+ baseColumn: never;
520
+ identity: undefined;
521
+ generated: undefined;
522
+ }, {}, {
523
+ length: number | undefined;
524
+ }>;
525
+ commandType: import("drizzle-orm/sqlite-core").SQLiteColumn<{
526
+ name: "command_type";
527
+ tableName: "operation_history";
528
+ dataType: "string";
529
+ columnType: "SQLiteText";
530
+ data: string;
531
+ driverParam: string;
532
+ notNull: true;
533
+ hasDefault: false;
534
+ isPrimaryKey: false;
535
+ isAutoincrement: false;
536
+ hasRuntimeDefault: false;
537
+ enumValues: [string, ...string[]];
538
+ baseColumn: never;
539
+ identity: undefined;
540
+ generated: undefined;
541
+ }, {}, {
542
+ length: number | undefined;
543
+ }>;
544
+ commandData: import("drizzle-orm/sqlite-core").SQLiteColumn<{
545
+ name: "command_data";
546
+ tableName: "operation_history";
547
+ dataType: "string";
548
+ columnType: "SQLiteText";
549
+ data: string;
550
+ driverParam: string;
551
+ notNull: true;
552
+ hasDefault: false;
553
+ isPrimaryKey: false;
554
+ isAutoincrement: false;
555
+ hasRuntimeDefault: false;
556
+ enumValues: [string, ...string[]];
557
+ baseColumn: never;
558
+ identity: undefined;
559
+ generated: undefined;
560
+ }, {}, {
561
+ length: number | undefined;
562
+ }>;
563
+ executedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
564
+ name: "executed_at";
565
+ tableName: "operation_history";
566
+ dataType: "date";
567
+ columnType: "SQLiteTimestamp";
568
+ data: Date;
569
+ driverParam: number;
570
+ notNull: true;
571
+ hasDefault: false;
572
+ isPrimaryKey: false;
573
+ isAutoincrement: false;
574
+ hasRuntimeDefault: false;
575
+ enumValues: undefined;
576
+ baseColumn: never;
577
+ identity: undefined;
578
+ generated: undefined;
579
+ }, {}, {}>;
580
+ isUndone: import("drizzle-orm/sqlite-core").SQLiteColumn<{
581
+ name: "is_undone";
582
+ tableName: "operation_history";
583
+ dataType: "boolean";
584
+ columnType: "SQLiteBoolean";
585
+ data: boolean;
586
+ driverParam: number;
587
+ notNull: true;
588
+ hasDefault: true;
589
+ isPrimaryKey: false;
590
+ isAutoincrement: false;
591
+ hasRuntimeDefault: false;
592
+ enumValues: undefined;
593
+ baseColumn: never;
594
+ identity: undefined;
595
+ generated: undefined;
596
+ }, {}, {}>;
597
+ };
598
+ dialect: "sqlite";
599
+ }>;
600
+ export type OperationHistory = typeof operationHistory.$inferSelect;
package/dist/db/schema.js CHANGED
@@ -11,6 +11,7 @@ export const tasks = sqliteTable('tasks', {
11
11
  isFocused: integer('is_focused', { mode: 'boolean' }).notNull().default(false),
12
12
  effort: text('effort'),
13
13
  dueDate: integer('due_date', { mode: 'timestamp' }),
14
+ completedAt: integer('completed_at', { mode: 'timestamp' }),
14
15
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
15
16
  updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
16
17
  });
@@ -32,3 +33,10 @@ export const pomodoroSessions = sqliteTable('pomodoro_sessions', {
32
33
  completedCount: integer('completed_count').notNull().default(0),
33
34
  updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
34
35
  });
36
+ export const operationHistory = sqliteTable('operation_history', {
37
+ id: text('id').primaryKey(),
38
+ commandType: text('command_type').notNull(),
39
+ commandData: text('command_data').notNull(), // JSON serialized
40
+ executedAt: integer('executed_at', { mode: 'timestamp' }).notNull(),
41
+ isUndone: integer('is_undone', { mode: 'boolean' }).notNull().default(false),
42
+ });
package/dist/i18n/en.d.ts CHANGED
@@ -243,6 +243,7 @@ export declare const en: {
243
243
  taskDetailTitle: string;
244
244
  taskDetailFooter: string;
245
245
  taskDetailStatus: string;
246
+ taskDetailCompletedAt: string;
246
247
  deleteConfirm: string;
247
248
  deleted: string;
248
249
  deleteCancelled: string;
@@ -670,6 +671,7 @@ export type TuiTranslations = {
670
671
  taskDetailTitle: string;
671
672
  taskDetailFooter: string;
672
673
  taskDetailStatus: string;
674
+ taskDetailCompletedAt?: string;
673
675
  deleteConfirm: string;
674
676
  deleted: string;
675
677
  deleteCancelled: string;
package/dist/i18n/en.js CHANGED
@@ -256,6 +256,7 @@ export const en = {
256
256
  taskDetailTitle: 'Task Details',
257
257
  taskDetailFooter: 'j/k=select i=comment d=delete P=link b/Esc=back',
258
258
  taskDetailStatus: 'Status',
259
+ taskDetailCompletedAt: 'Completed',
259
260
  deleteConfirm: 'Delete "{title}"? (y/n)',
260
261
  deleted: 'Deleted: "{title}"',
261
262
  deleteCancelled: 'Delete cancelled',
package/dist/i18n/ja.js CHANGED
@@ -256,6 +256,7 @@ export const ja = {
256
256
  taskDetailTitle: 'タスク詳細',
257
257
  taskDetailFooter: 'j/k=選択 i=コメント d=削除 P=紐づけ b/Esc=戻る',
258
258
  taskDetailStatus: 'ステータス',
259
+ taskDetailCompletedAt: '完了日',
259
260
  deleteConfirm: '「{title}」を削除しますか? (y/n)',
260
261
  deleted: '削除しました: 「{title}」',
261
262
  deleteCancelled: '削除をキャンセルしました',
package/dist/ui/App.js CHANGED
@@ -407,6 +407,7 @@ function AppContent({ onOpenSettings }) {
407
407
  toStatus: 'done',
408
408
  fromWaitingFor: task.waitingFor,
409
409
  toWaitingFor: null,
410
+ fromCompletedAt: task.completedAt,
410
411
  description: fmt(i18n.tui.completed, { title: task.title }),
411
412
  });
412
413
  await history.execute(command);
@@ -420,6 +421,7 @@ function AppContent({ onOpenSettings }) {
420
421
  toStatus: status,
421
422
  fromWaitingFor: task.waitingFor,
422
423
  toWaitingFor: null,
424
+ fromCompletedAt: task.completedAt,
423
425
  description: fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }),
424
426
  });
425
427
  await history.execute(command);
@@ -433,6 +435,7 @@ function AppContent({ onOpenSettings }) {
433
435
  toStatus: 'waiting',
434
436
  fromWaitingFor: task.waitingFor,
435
437
  toWaitingFor: waitingFor.trim(),
438
+ fromCompletedAt: task.completedAt,
436
439
  description: fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }),
437
440
  });
438
441
  await history.execute(command);
@@ -1109,7 +1112,7 @@ function AppContent({ onOpenSettings }) {
1109
1112
  return;
1110
1113
  }
1111
1114
  // Move to inbox
1112
- if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects' && currentTab !== 'done') {
1115
+ if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects') {
1113
1116
  const task = currentTasks[selectedTaskIndex];
1114
1117
  moveTaskToStatus(task, 'inbox').then(() => {
1115
1118
  if (selectedTaskIndex >= currentTasks.length - 1) {
@@ -1188,17 +1191,17 @@ function AppContent({ onOpenSettings }) {
1188
1191
  const count = tasks[tab].length;
1189
1192
  const label = `${index + 1}:${getTabLabel(tab)}(${count})`;
1190
1193
  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));
1191
- }) }), 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})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] })] }), _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) => {
1194
+ }) }), 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})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] }), selectedTask.status === 'done' && (_jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailCompletedAt || 'Completed', ": "] }), _jsx(Text, { color: theme.colors.accent, children: (selectedTask.completedAt ?? selectedTask.updatedAt).toLocaleString() })] }))] }), _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) => {
1192
1195
  const isSelected = index === selectedCommentIndex && mode === 'task-detail';
1193
1196
  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));
1194
1197
  })) }), 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' && (theme.uiStyle === 'titled-box' ? (_jsx(TitledBox, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : getTabLabel(currentTab), borderColor: theme.colors.border, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
1195
1198
  const parentProject = getParentProject(task.parentId);
1196
1199
  const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
1197
- return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
1200
+ return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress, showStatus: mode === 'project-detail' }, task.id));
1198
1201
  })) })) : (_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) => {
1199
1202
  const parentProject = getParentProject(task.parentId);
1200
1203
  const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
1201
- return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress }, task.id));
1204
+ return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title, progress: progress, showStatus: mode === 'project-detail' }, task.id));
1202
1205
  })) }))), (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
1203
1206
  ? `${i18n.tui.newTask}[${selectedProject.title}] `
1204
1207
  : 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 === 'context-filter' && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: i18n.tui.context?.filter || 'Filter by context' }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
@@ -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 React, { useState, useEffect, useCallback } from 'react';
3
3
  import { Box, Text, useInput, useApp, useStdout } from 'ink';
4
4
  import TextInput from 'ink-text-input';
@@ -14,6 +14,7 @@ import { SearchResults } from './SearchResults.js';
14
14
  import { HelpModal } from './HelpModal.js';
15
15
  import { InsightsModal } from './InsightsModal.js';
16
16
  import { CalendarModal } from './CalendarModal.js';
17
+ import { StatusBadge } from './StatusBadge.js';
17
18
  import { Clock } from './Clock.js';
18
19
  import { CalendarEvents } from './CalendarEvents.js';
19
20
  import { PomodoroBattleUI, BattleMessage, getBattleMessage, BATTLE_COMMANDS } from './PomodoroBattleUI.js';
@@ -346,6 +347,7 @@ export function GtdDQ({ onOpenSettings }) {
346
347
  toStatus: 'done',
347
348
  fromWaitingFor: task.waitingFor,
348
349
  toWaitingFor: null,
350
+ fromCompletedAt: task.completedAt,
349
351
  description: fmt(i18n.tui.completed, { title: task.title }),
350
352
  });
351
353
  await history.execute(command);
@@ -359,6 +361,7 @@ export function GtdDQ({ onOpenSettings }) {
359
361
  toStatus: status,
360
362
  fromWaitingFor: task.waitingFor,
361
363
  toWaitingFor: null,
364
+ fromCompletedAt: task.completedAt,
362
365
  description: fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status[status] }),
363
366
  });
364
367
  await history.execute(command);
@@ -372,6 +375,7 @@ export function GtdDQ({ onOpenSettings }) {
372
375
  toStatus: 'waiting',
373
376
  fromWaitingFor: task.waitingFor,
374
377
  toWaitingFor: waitingFor.trim(),
378
+ fromCompletedAt: task.completedAt,
375
379
  description: fmt(i18n.tui.movedToWaiting, { title: task.title, person: waitingFor.trim() }),
376
380
  });
377
381
  await history.execute(command);
@@ -645,10 +649,20 @@ export function GtdDQ({ onOpenSettings }) {
645
649
  // Handle task-detail mode
646
650
  if (mode === 'task-detail') {
647
651
  if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
652
+ // If came from project-detail, go back to project-detail
653
+ if (selectedProject) {
654
+ setMode('project-detail');
655
+ const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
656
+ if (taskIndex >= 0) {
657
+ setSelectedTaskIndex(taskIndex);
658
+ }
659
+ }
660
+ else {
661
+ setMode('normal');
662
+ }
648
663
  setSelectedTask(null);
649
664
  setTaskComments([]);
650
665
  setSelectedCommentIndex(0);
651
- setMode('normal');
652
666
  return;
653
667
  }
654
668
  // Navigate comments
@@ -868,6 +882,14 @@ export function GtdDQ({ onOpenSettings }) {
868
882
  });
869
883
  return;
870
884
  }
885
+ // Enter to view task details within project
886
+ if (key.return && projectTasks.length > 0) {
887
+ const task = projectTasks[selectedTaskIndex];
888
+ setSelectedTask(task);
889
+ loadTaskComments(task.id);
890
+ setMode('task-detail');
891
+ return;
892
+ }
871
893
  return;
872
894
  }
873
895
  if (message)
@@ -1006,7 +1028,7 @@ export function GtdDQ({ onOpenSettings }) {
1006
1028
  return;
1007
1029
  }
1008
1030
  // Move to inbox
1009
- if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects' && currentTab !== 'done') {
1031
+ if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects') {
1010
1032
  moveTaskToStatus(task, 'inbox').then(() => {
1011
1033
  if (selectedTaskIndex >= currentTasks.length - 1) {
1012
1034
  setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
@@ -1157,7 +1179,7 @@ export function GtdDQ({ onOpenSettings }) {
1157
1179
  }) })] })) : mode === 'set-context' && currentTasks.length > 0 ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.setContext || 'Set context for', ": ", currentTasks[selectedTaskIndex]?.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['clear', ...availableContexts, 'new'].map((ctx, index) => {
1158
1180
  const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
1159
1181
  return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
1160
- }) })] })) : mode === 'set-effort' && getCurrentTask() ? (_jsx(Box, { flexDirection: "column", children: _jsx(TitledBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: Math.min(40, terminalWidth - 4), minHeight: EFFORT_OPTIONS.length, isActive: true, children: EFFORT_OPTIONS.map((option, index) => (_jsxs(Text, { color: index === effortSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === effortSelectIndex, children: [index === effortSelectIndex ? '▶ ' : ' ', option.label] }, option.label))) }) })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '▶ ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(TitledBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, 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, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(TitledBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
1182
+ }) })] })) : mode === 'set-effort' && getCurrentTask() ? (_jsx(Box, { flexDirection: "column", children: _jsx(TitledBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: Math.min(40, terminalWidth - 4), minHeight: EFFORT_OPTIONS.length, isActive: true, children: EFFORT_OPTIONS.map((option, index) => (_jsxs(Text, { color: index === effortSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === effortSelectIndex, children: [index === effortSelectIndex ? '▶ ' : ' ', option.label] }, option.label))) }) })) : mode === 'select-project' && taskToLink ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? '▶ ' : ' ', project.title] }, project.id))) })] })) : mode === 'confirm-delete' && taskToDelete ? (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: theme.colors.accent, bold: true, children: fmt(i18n.tui.deleteConfirm || 'Delete "{title}"? (y/n)', { title: taskToDelete.title }) }) })) : (mode === 'task-detail' || mode === 'add-comment') && selectedTask ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(TitledBoxInline, { title: i18n.tui.taskDetailTitle || 'Task Details', width: terminalWidth - 4, minHeight: 4, isActive: true, 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, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailStatus || 'Status', ": "] }), _jsxs(Text, { color: theme.colors.accent, children: [i18n.status[selectedTask.status], selectedTask.waitingFor && ` (${selectedTask.waitingFor})`] })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.context?.label || 'Context', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.context ? `@${selectedTask.context}` : (i18n.tui.context?.none || 'No context') })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.label || 'Effort', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.effort ? (i18n.tui.effort?.[selectedTask.effort] || selectedTask.effort) : '-' })] }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.focus?.label || 'Focus', ": "] }), _jsx(Text, { color: theme.colors.accent, children: selectedTask.isFocused ? '★' : '-' })] }), selectedTask.status === 'done' && (_jsxs(Box, { children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.taskDetailCompletedAt || 'Completed', ": "] }), _jsx(Text, { color: theme.colors.accent, children: (selectedTask.completedAt ?? selectedTask.updatedAt).toLocaleString() })] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(TitledBoxInline, { title: `${i18n.tui.comments || 'Comments'} (${taskComments.length})`, width: terminalWidth - 4, minHeight: 5, isActive: mode === 'task-detail', children: taskComments.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noComments || 'No comments yet' })) : (taskComments.map((comment, index) => {
1161
1183
  const isSelected = index === selectedCommentIndex && mode === 'task-detail';
1162
1184
  return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.textMuted, children: [isSelected ? '▶ ' : ' ', "[", comment.createdAt.toLocaleString(), "]"] }), _jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [' ', comment.content] })] }, comment.id));
1163
1185
  })) }) })] })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(TitledBoxInline, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : 'GTD', width: leftPaneWidth, minHeight: 8, isActive: paneFocus === 'tabs' && mode !== 'project-detail', children: mode === 'project-detail' ? (_jsx(Text, { color: theme.colors.textMuted, children: "\u2190 Esc/b: back" })) : (TABS.map((tab, index) => {
@@ -1180,7 +1202,7 @@ export function GtdDQ({ onOpenSettings }) {
1180
1202
  ].join('');
1181
1203
  const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(focusPrefix) - getDisplayWidth(effortBadge) - getDisplayWidth(suffix);
1182
1204
  const displayTitle = truncateString(task.title, availableWidth);
1183
- return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, focusPrefix, effortBadge, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
1205
+ return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, mode === 'project-detail' && _jsxs(_Fragment, { children: [_jsx(StatusBadge, { status: task.status }), " "] }), focusPrefix, effortBadge, displayTitle, task.waitingFor && _jsxs(Text, { color: theme.colors.muted, children: [" (", task.waitingFor, ")"] }), task.context && _jsxs(Text, { color: theme.colors.muted, children: [" @", task.context] }), parentProject && _jsxs(Text, { color: theme.colors.muted, children: [" [", parentProject.title, "]"] }), progress && _jsxs(Text, { color: theme.colors.muted, children: [" [", progress.completed, "/", progress.total, "]"] })] }, task.id));
1184
1206
  })) }) })] })), (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
1185
1207
  ? `${i18n.tui.newTask}[${selectedProject.title}] `
1186
1208
  : i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder })] })), 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: "" })] })), mode === 'add-context' && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "New context: " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter context name..." })] })), mode === 'add-comment' && selectedTask && (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.addComment || 'Add comment', ": "] }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: "Enter comment..." })] })), mode === 'search' && (_jsx(SearchBar, { value: searchQuery, onChange: handleSearchChange, onSubmit: handleInputSubmit })), mode === 'search' && searchQuery && (_jsx(SearchResults, { results: searchResults, selectedIndex: searchResultIndex, query: searchQuery })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsx(Text, { color: theme.colors.textMuted, children: mode === 'task-detail'