floq 1.3.3 → 1.4.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.
@@ -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';
@@ -7,12 +7,14 @@ import { v4 as uuidv4 } from 'uuid';
7
7
  import { getDb, schema } from '../../db/index.js';
8
8
  import { t, fmt } from '../../i18n/index.js';
9
9
  import { useTheme } from '../theme/index.js';
10
- import { isTursoEnabled, getContexts, addContext, getLocale, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode } from '../../config.js';
11
- import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
10
+ import { isTursoEnabled, getContexts, addContext, getLocale, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode, getFocusFilter, setFocusFilter } from '../../config.js';
11
+ import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, SetFocusCommand, SetEffortCommand, } from '../history/index.js';
12
12
  import { SearchBar } from './SearchBar.js';
13
13
  import { SearchResults } from './SearchResults.js';
14
14
  import { HelpModal } from './HelpModal.js';
15
+ import { InsightsModal } from './InsightsModal.js';
15
16
  import { CalendarModal } from './CalendarModal.js';
17
+ import { StatusBadge } from './StatusBadge.js';
16
18
  import { Clock } from './Clock.js';
17
19
  import { CalendarEvents } from './CalendarEvents.js';
18
20
  import { PomodoroBattleUI, BattleMessage, getBattleMessage, BATTLE_COMMANDS } from './PomodoroBattleUI.js';
@@ -155,6 +157,9 @@ export function GtdDQ({ onOpenSettings }) {
155
157
  const [searchQuery, setSearchQuery] = useState('');
156
158
  const [searchResults, setSearchResults] = useState([]);
157
159
  const [searchResultIndex, setSearchResultIndex] = useState(0);
160
+ // Focus filter state
161
+ const [focusFilterState, setFocusFilterState] = useState(getFocusFilter());
162
+ const [effortSelectIndex, setEffortSelectIndex] = useState(0);
158
163
  // Pomodoro focus mode state
159
164
  const [focusMode, setFocusModeState] = useState(() => getPomodoroFocusMode());
160
165
  const toggleFocusMode = useCallback(() => {
@@ -181,6 +186,17 @@ export function GtdDQ({ onOpenSettings }) {
181
186
  const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
182
187
  const currentTab = TABS[currentTabIndex];
183
188
  const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
189
+ const EFFORT_OPTIONS = [
190
+ { value: 'small', label: i18n.tui.effort?.small || 'Small' },
191
+ { value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
192
+ { value: 'large', label: i18n.tui.effort?.large || 'Large' },
193
+ { value: null, label: i18n.tui.effort?.clear || 'Clear' },
194
+ ];
195
+ const getCurrentTask = () => {
196
+ if (currentTasks.length === 0)
197
+ return undefined;
198
+ return currentTasks[selectedTaskIndex];
199
+ };
184
200
  const loadTasks = useCallback(async () => {
185
201
  const db = getDb();
186
202
  const newTasks = {
@@ -214,6 +230,9 @@ export function GtdDQ({ onOpenSettings }) {
214
230
  allTasks = allTasks.filter(t => t.context === contextFilter);
215
231
  }
216
232
  }
233
+ if (focusFilterState) {
234
+ allTasks = allTasks.filter(t => t.isFocused);
235
+ }
217
236
  newTasks[status] = allTasks;
218
237
  }
219
238
  newTasks.projects = await db
@@ -233,7 +252,7 @@ export function GtdDQ({ onOpenSettings }) {
233
252
  setProjectProgress(progress);
234
253
  setTasks(newTasks);
235
254
  setAvailableContexts(getContexts());
236
- }, [contextFilter]);
255
+ }, [contextFilter, focusFilterState]);
237
256
  const loadProjectTasks = useCallback(async (projectId) => {
238
257
  const db = getDb();
239
258
  const children = await db
@@ -430,6 +449,48 @@ export function GtdDQ({ onOpenSettings }) {
430
449
  setMessage(description);
431
450
  await loadTasks();
432
451
  }, [i18n.tui.context, loadTasks, history]);
452
+ const toggleTaskFocus = async () => {
453
+ const task = getCurrentTask();
454
+ if (!task)
455
+ return;
456
+ const newFocused = !task.isFocused;
457
+ const description = newFocused
458
+ ? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
459
+ : fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
460
+ const command = new SetFocusCommand({
461
+ taskId: task.id,
462
+ fromFocused: task.isFocused,
463
+ toFocused: newFocused,
464
+ description,
465
+ });
466
+ await history.execute(command);
467
+ setMessage(description);
468
+ await loadTasks();
469
+ };
470
+ const toggleFocusFilter = () => {
471
+ const newValue = !focusFilterState;
472
+ setFocusFilterState(newValue);
473
+ setFocusFilter(newValue);
474
+ setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
475
+ };
476
+ const setTaskEffort = async (effort) => {
477
+ const task = getCurrentTask();
478
+ if (!task)
479
+ return;
480
+ const description = effort
481
+ ? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
482
+ : fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
483
+ const command = new SetEffortCommand({
484
+ taskId: task.id,
485
+ fromEffort: task.effort,
486
+ toEffort: effort,
487
+ description,
488
+ });
489
+ await history.execute(command);
490
+ setMessage(description);
491
+ setMode('normal');
492
+ await loadTasks();
493
+ };
433
494
  const handleInputSubmit = async (value) => {
434
495
  // Handle search mode submit
435
496
  if (mode === 'search') {
@@ -494,8 +555,8 @@ export function GtdDQ({ onOpenSettings }) {
494
555
  }
495
556
  };
496
557
  useInput((input, key) => {
497
- // Handle help mode - let HelpModal handle its own input
498
- if (mode === 'help') {
558
+ // Handle help/insights mode - let modals handle their own input
559
+ if (mode === 'help' || mode === 'insights') {
499
560
  return;
500
561
  }
501
562
  // Battle command navigation (j/k) - when timer is running and in focus mode
@@ -585,10 +646,20 @@ export function GtdDQ({ onOpenSettings }) {
585
646
  // Handle task-detail mode
586
647
  if (mode === 'task-detail') {
587
648
  if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
649
+ // If came from project-detail, go back to project-detail
650
+ if (selectedProject) {
651
+ setMode('project-detail');
652
+ const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
653
+ if (taskIndex >= 0) {
654
+ setSelectedTaskIndex(taskIndex);
655
+ }
656
+ }
657
+ else {
658
+ setMode('normal');
659
+ }
588
660
  setSelectedTask(null);
589
661
  setTaskComments([]);
590
662
  setSelectedCommentIndex(0);
591
- setMode('normal');
592
663
  return;
593
664
  }
594
665
  // Navigate comments
@@ -730,6 +801,26 @@ export function GtdDQ({ onOpenSettings }) {
730
801
  }
731
802
  return;
732
803
  }
804
+ // Handle set-effort mode
805
+ if (mode === 'set-effort') {
806
+ if (key.escape) {
807
+ setMode('normal');
808
+ return;
809
+ }
810
+ if (input === 'j' || key.downArrow) {
811
+ setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
812
+ return;
813
+ }
814
+ if (input === 'k' || key.upArrow) {
815
+ setEffortSelectIndex(prev => Math.max(prev - 1, 0));
816
+ return;
817
+ }
818
+ if (key.return) {
819
+ setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
820
+ return;
821
+ }
822
+ return;
823
+ }
733
824
  // Handle select-project mode
734
825
  if (mode === 'select-project') {
735
826
  if (key.escape) {
@@ -788,6 +879,14 @@ export function GtdDQ({ onOpenSettings }) {
788
879
  });
789
880
  return;
790
881
  }
882
+ // Enter to view task details within project
883
+ if (key.return && projectTasks.length > 0) {
884
+ const task = projectTasks[selectedTaskIndex];
885
+ setSelectedTask(task);
886
+ loadTaskComments(task.id);
887
+ setMode('task-detail');
888
+ return;
889
+ }
791
890
  return;
792
891
  }
793
892
  if (message)
@@ -801,6 +900,10 @@ export function GtdDQ({ onOpenSettings }) {
801
900
  setMode('help');
802
901
  return;
803
902
  }
903
+ if (input === 'I') {
904
+ setMode('insights');
905
+ return;
906
+ }
804
907
  if (input === 'C') {
805
908
  setMode('calendar');
806
909
  return;
@@ -922,7 +1025,7 @@ export function GtdDQ({ onOpenSettings }) {
922
1025
  return;
923
1026
  }
924
1027
  // Move to inbox
925
- if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects' && currentTab !== 'done') {
1028
+ if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects') {
926
1029
  moveTaskToStatus(task, 'inbox').then(() => {
927
1030
  if (selectedTaskIndex >= currentTasks.length - 1) {
928
1031
  setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
@@ -958,6 +1061,24 @@ export function GtdDQ({ onOpenSettings }) {
958
1061
  setMode('set-context');
959
1062
  return;
960
1063
  }
1064
+ // Toggle focus on task
1065
+ if (input === 'g') {
1066
+ toggleTaskFocus();
1067
+ return;
1068
+ }
1069
+ // Toggle focus filter
1070
+ if (input === 'G') {
1071
+ toggleFocusFilter();
1072
+ return;
1073
+ }
1074
+ // Set effort
1075
+ if (input === 'E') {
1076
+ if (getCurrentTask()) {
1077
+ setEffortSelectIndex(0);
1078
+ setMode('set-effort');
1079
+ }
1080
+ return;
1081
+ }
961
1082
  }
962
1083
  // Undo
963
1084
  if (input === 'u') {
@@ -1035,6 +1156,10 @@ export function GtdDQ({ onOpenSettings }) {
1035
1156
  if (mode === 'help') {
1036
1157
  return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
1037
1158
  }
1159
+ // Insights modal overlay
1160
+ if (mode === 'insights') {
1161
+ return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
1162
+ }
1038
1163
  // Calendar modal overlay
1039
1164
  if (mode === 'calendar') {
1040
1165
  return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
@@ -1045,13 +1170,13 @@ export function GtdDQ({ onOpenSettings }) {
1045
1170
  const battleWidth = terminalWidth - 4;
1046
1171
  return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(PomodoroBattleUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, jobClass: jobClass, level: playerLevel, totalCompleted: tasks.done.length, width: battleWidth, selectedCommand: selectedCommand }), message && (_jsx(BattleMessage, { message: message, isNew: true })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsxs(Text, { color: theme.colors.textMuted, children: ["a=", i18n.tui.keyBar.add, " f=focus off"] })] }) })] }));
1047
1172
  }
1048
- 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.accent, children: jobClass }), _jsx(Text, { color: theme.colors.text, children: " Lv." }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: Math.floor(tasks.done.length / 5) + 1 }), _jsx(Text, { color: theme.colors.text, children: " HP " }), _jsx(Text, { bold: true, color: theme.colors.statusNext, children: tasks.inbox.length + tasks.next.length }), _jsxs(Text, { color: theme.colors.textMuted, children: ["/", tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length] }), _jsx(Text, { color: theme.colors.text, children: " MP " }), _jsx(Text, { bold: true, color: theme.colors.statusWaiting, children: tasks.projects.length }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : '' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {}), _jsx(Text, { color: theme.colors.textMuted, children: " ?=help q=quit" })] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroBattleUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, jobClass: jobClass, level: Math.floor(tasks.done.length / 5) + 1, totalCompleted: tasks.done.length, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
1173
+ 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.accent, children: jobClass }), _jsx(Text, { color: theme.colors.text, children: " Lv." }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: Math.floor(tasks.done.length / 5) + 1 }), _jsx(Text, { color: theme.colors.text, children: " HP " }), _jsx(Text, { bold: true, color: theme.colors.statusNext, children: tasks.inbox.length + tasks.next.length }), _jsxs(Text, { color: theme.colors.textMuted, children: ["/", tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length] }), _jsx(Text, { color: theme.colors.text, children: " MP " }), _jsx(Text, { bold: true, color: theme.colors.statusWaiting, children: tasks.projects.length }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : '' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] })), focusFilterState && (_jsxs(Text, { color: theme.colors.accent, children: [" ", i18n.tui.focus?.focused || '★ Focused'] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {}), _jsx(Text, { color: theme.colors.textMuted, children: " ?=help q=quit" })] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroBattleUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, jobClass: jobClass, level: Math.floor(tasks.done.length / 5) + 1, totalCompleted: tasks.done.length, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
1049
1174
  const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
1050
1175
  return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
1051
1176
  }) })] })) : 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) => {
1052
1177
  const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
1053
1178
  return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
1054
- }) })] })) : 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') })] })] }), _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) => {
1179
+ }) })] })) : 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) => {
1055
1180
  const isSelected = index === selectedCommentIndex && mode === 'task-detail';
1056
1181
  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));
1057
1182
  })) }) })] })) : (_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) => {
@@ -1062,6 +1187,8 @@ export function GtdDQ({ onOpenSettings }) {
1062
1187
  const isSelected = (paneFocus === 'tasks' || mode === 'project-detail') && index === selectedTaskIndex;
1063
1188
  const parentProject = getParentProject(task.parentId);
1064
1189
  const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
1190
+ const focusPrefix = task.isFocused ? '★ ' : '';
1191
+ const effortBadge = task.effort ? `[${{ 'small': 'S', 'medium': 'M', 'large': 'L' }[task.effort]}] ` : '';
1065
1192
  // Calculate available width for title
1066
1193
  const prefix = isSelected ? '▶ ' : ' ';
1067
1194
  const suffix = [
@@ -1070,9 +1197,9 @@ export function GtdDQ({ onOpenSettings }) {
1070
1197
  parentProject ? ` [${parentProject.title}]` : '',
1071
1198
  progress ? ` [${progress.completed}/${progress.total}]` : '',
1072
1199
  ].join('');
1073
- const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(suffix);
1200
+ const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(focusPrefix) - getDisplayWidth(effortBadge) - getDisplayWidth(suffix);
1074
1201
  const displayTitle = truncateString(task.title, availableWidth);
1075
- return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, 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));
1202
+ 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));
1076
1203
  })) }) })] })), (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
1077
1204
  ? `${i18n.tui.newTask}[${selectedProject.title}] `
1078
1205
  : 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'
@@ -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, useStdout } from 'ink';
4
4
  import TextInput from 'ink-text-input';
@@ -7,13 +7,15 @@ import { v4 as uuidv4 } from 'uuid';
7
7
  import { getDb, schema } from '../../db/index.js';
8
8
  import { t, fmt } from '../../i18n/index.js';
9
9
  import { useTheme } from '../theme/index.js';
10
- import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode } from '../../config.js';
10
+ import { isTursoEnabled, getContexts, addContext, getContextFilter, setContextFilter as saveContextFilter, getPomodoroFocusMode, setPomodoroFocusMode, getFocusFilter, setFocusFilter } from '../../config.js';
11
11
  import { VERSION } from '../../version.js';
12
- import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
12
+ import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, SetFocusCommand, SetEffortCommand, } from '../history/index.js';
13
13
  import { SearchBar } from './SearchBar.js';
14
14
  import { SearchResults } from './SearchResults.js';
15
15
  import { HelpModal } from './HelpModal.js';
16
+ import { InsightsModal } from './InsightsModal.js';
16
17
  import { CalendarModal } from './CalendarModal.js';
18
+ import { StatusBadge } from './StatusBadge.js';
17
19
  import { MarioBoxInline } from './MarioBox.js';
18
20
  import { Clock } from './Clock.js';
19
21
  import { CalendarEvents } from './CalendarEvents.js';
@@ -97,6 +99,8 @@ export function GtdMario({ onOpenSettings }) {
97
99
  const [searchQuery, setSearchQuery] = useState('');
98
100
  const [searchResults, setSearchResults] = useState([]);
99
101
  const [searchResultIndex, setSearchResultIndex] = useState(0);
102
+ const [focusFilter, setFocusFilterState] = useState(getFocusFilter());
103
+ const [effortSelectIndex, setEffortSelectIndex] = useState(0);
100
104
  // Focus mode state (can be toggled during pomodoro)
101
105
  const [focusMode, setFocusModeState] = useState(() => getPomodoroFocusMode());
102
106
  const toggleFocusMode = useCallback(() => {
@@ -132,6 +136,17 @@ export function GtdMario({ onOpenSettings }) {
132
136
  const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
133
137
  const currentTab = TABS[currentTabIndex];
134
138
  const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
139
+ const getCurrentTask = () => {
140
+ if (currentTasks.length === 0)
141
+ return undefined;
142
+ return currentTasks[selectedTaskIndex];
143
+ };
144
+ const EFFORT_OPTIONS = [
145
+ { value: 'small', label: i18n.tui.effort?.small || 'Small' },
146
+ { value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
147
+ { value: 'large', label: i18n.tui.effort?.large || 'Large' },
148
+ { value: null, label: i18n.tui.effort?.clear || 'Clear' },
149
+ ];
135
150
  const loadTasks = useCallback(async () => {
136
151
  const db = getDb();
137
152
  const newTasks = {
@@ -165,6 +180,9 @@ export function GtdMario({ onOpenSettings }) {
165
180
  allTasks = allTasks.filter(t => t.context === contextFilter);
166
181
  }
167
182
  }
183
+ if (focusFilter) {
184
+ allTasks = allTasks.filter(t => t.isFocused);
185
+ }
168
186
  newTasks[status] = allTasks;
169
187
  }
170
188
  newTasks.projects = await db
@@ -184,7 +202,7 @@ export function GtdMario({ onOpenSettings }) {
184
202
  setProjectProgress(progress);
185
203
  setTasks(newTasks);
186
204
  setAvailableContexts(getContexts());
187
- }, [contextFilter]);
205
+ }, [contextFilter, focusFilter]);
188
206
  const loadProjectTasks = useCallback(async (projectId) => {
189
207
  const db = getDb();
190
208
  const children = await db
@@ -377,6 +395,48 @@ export function GtdMario({ onOpenSettings }) {
377
395
  setMessage(description);
378
396
  await loadTasks();
379
397
  }, [i18n.tui.context, loadTasks, history]);
398
+ const toggleTaskFocus = async () => {
399
+ const task = getCurrentTask();
400
+ if (!task)
401
+ return;
402
+ const newFocused = !task.isFocused;
403
+ const description = newFocused
404
+ ? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
405
+ : fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
406
+ const command = new SetFocusCommand({
407
+ taskId: task.id,
408
+ fromFocused: task.isFocused,
409
+ toFocused: newFocused,
410
+ description,
411
+ });
412
+ await history.execute(command);
413
+ setMessage(description);
414
+ await loadTasks();
415
+ };
416
+ const toggleFocusFilter = () => {
417
+ const newValue = !focusFilter;
418
+ setFocusFilterState(newValue);
419
+ setFocusFilter(newValue);
420
+ setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
421
+ };
422
+ const setTaskEffort = async (effort) => {
423
+ const task = getCurrentTask();
424
+ if (!task)
425
+ return;
426
+ const description = effort
427
+ ? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
428
+ : fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
429
+ const command = new SetEffortCommand({
430
+ taskId: task.id,
431
+ fromEffort: task.effort,
432
+ toEffort: effort,
433
+ description,
434
+ });
435
+ await history.execute(command);
436
+ setMessage(description);
437
+ setMode('normal');
438
+ await loadTasks();
439
+ };
380
440
  const handleInputSubmit = async (value) => {
381
441
  if (mode === 'search') {
382
442
  if (searchResults.length > 0) {
@@ -439,7 +499,7 @@ export function GtdMario({ onOpenSettings }) {
439
499
  }
440
500
  };
441
501
  useInput((input, key) => {
442
- if (mode === 'help') {
502
+ if (mode === 'help' || mode === 'insights') {
443
503
  return;
444
504
  }
445
505
  // Focus mode key handling - simplified controls
@@ -509,10 +569,20 @@ export function GtdMario({ onOpenSettings }) {
509
569
  }
510
570
  if (mode === 'task-detail') {
511
571
  if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
572
+ // If came from project-detail, go back to project-detail
573
+ if (selectedProject) {
574
+ setMode('project-detail');
575
+ const taskIndex = projectTasks.findIndex(t => t.id === selectedTask?.id);
576
+ if (taskIndex >= 0) {
577
+ setSelectedTaskIndex(taskIndex);
578
+ }
579
+ }
580
+ else {
581
+ setMode('normal');
582
+ }
512
583
  setSelectedTask(null);
513
584
  setTaskComments([]);
514
585
  setSelectedCommentIndex(0);
515
- setMode('normal');
516
586
  return;
517
587
  }
518
588
  if (key.upArrow || input === 'k') {
@@ -644,6 +714,25 @@ export function GtdMario({ onOpenSettings }) {
644
714
  }
645
715
  return;
646
716
  }
717
+ if (mode === 'set-effort') {
718
+ if (key.escape) {
719
+ setMode('normal');
720
+ return;
721
+ }
722
+ if (input === 'j' || key.downArrow) {
723
+ setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
724
+ return;
725
+ }
726
+ if (input === 'k' || key.upArrow) {
727
+ setEffortSelectIndex(prev => Math.max(prev - 1, 0));
728
+ return;
729
+ }
730
+ if (key.return) {
731
+ setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
732
+ return;
733
+ }
734
+ return;
735
+ }
647
736
  if (mode === 'select-project') {
648
737
  if (key.escape) {
649
738
  const wasFromTaskDetail = selectedTask !== null;
@@ -700,6 +789,14 @@ export function GtdMario({ onOpenSettings }) {
700
789
  });
701
790
  return;
702
791
  }
792
+ // Enter to view task details within project
793
+ if (key.return && projectTasks.length > 0) {
794
+ const task = projectTasks[selectedTaskIndex];
795
+ setSelectedTask(task);
796
+ loadTaskComments(task.id);
797
+ setMode('task-detail');
798
+ return;
799
+ }
703
800
  return;
704
801
  }
705
802
  if (message)
@@ -712,6 +809,10 @@ export function GtdMario({ onOpenSettings }) {
712
809
  setMode('help');
713
810
  return;
714
811
  }
812
+ if (input === 'I') {
813
+ setMode('insights');
814
+ return;
815
+ }
715
816
  if (input === 'C') {
716
817
  setMode('calendar');
717
818
  return;
@@ -824,7 +925,7 @@ export function GtdMario({ onOpenSettings }) {
824
925
  setMode('move-to-waiting');
825
926
  return;
826
927
  }
827
- if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects' && currentTab !== 'done') {
928
+ if (input === 'i' && currentTab !== 'inbox' && currentTab !== 'projects') {
828
929
  moveTaskToStatus(task, 'inbox').then(() => {
829
930
  if (selectedTaskIndex >= currentTasks.length - 1) {
830
931
  setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
@@ -857,6 +958,24 @@ export function GtdMario({ onOpenSettings }) {
857
958
  setMode('set-context');
858
959
  return;
859
960
  }
961
+ // Toggle focus on task
962
+ if (input === 'g') {
963
+ toggleTaskFocus();
964
+ return;
965
+ }
966
+ // Toggle focus filter
967
+ if (input === 'G') {
968
+ toggleFocusFilter();
969
+ return;
970
+ }
971
+ // Set effort
972
+ if (input === 'E') {
973
+ if (getCurrentTask()) {
974
+ setEffortSelectIndex(0);
975
+ setMode('set-effort');
976
+ }
977
+ return;
978
+ }
860
979
  }
861
980
  if (input === 'u') {
862
981
  history.undo().then((didUndo) => {
@@ -928,6 +1047,9 @@ export function GtdMario({ onOpenSettings }) {
928
1047
  if (mode === 'help') {
929
1048
  return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
930
1049
  }
1050
+ if (mode === 'insights') {
1051
+ return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
1052
+ }
931
1053
  if (mode === 'calendar') {
932
1054
  return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
933
1055
  }
@@ -942,13 +1064,13 @@ export function GtdMario({ onOpenSettings }) {
942
1064
  const marioWidth = terminalWidth - 4;
943
1065
  return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(PomodoroMarioUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, score: score, coins: coins, lives: lives, world: world, width: marioWidth, animationFrame: animationFrame }), message && (_jsx(MarioMessage, { message: message, isNew: true })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.accent, children: "\u2328\uFE0F " }), _jsxs(Text, { color: theme.colors.textMuted, children: ["a=", i18n.tui.keyBar.add, " Space=", pomodoro.isPaused ? 'resume' : 'pause', " S=skip X=stop f=focus off"] })] }) })] }));
944
1066
  }
945
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "WORLD " }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [(new Date().getMonth() + 1), "-", new Date().getDate()] }), _jsx(Text, { color: theme.colors.text, children: " " }), _jsx(Text, { color: theme.colors.secondary, children: "x" }), _jsx(Text, { color: theme.colors.text, children: (tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length + tasks.projects.length).toString().padStart(2, '0') }), _jsxs(Text, { color: theme.colors.textMuted, children: [" FLOQ v", VERSION] }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : ' [LOCAL]' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {})] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroMarioUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, score: tasks.done.length * 100, coins: pomodoro.state.completedCount, lives: Math.floor(tasks.done.length / 10) + 3, world: `${new Date().getMonth() + 1}-${new Date().getDate()}`, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
1067
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "WORLD " }), _jsxs(Text, { color: theme.colors.primary, bold: true, children: [(new Date().getMonth() + 1), "-", new Date().getDate()] }), _jsx(Text, { color: theme.colors.text, children: " " }), _jsx(Text, { color: theme.colors.secondary, children: "x" }), _jsx(Text, { color: theme.colors.text, children: (tasks.inbox.length + tasks.next.length + tasks.waiting.length + tasks.someday.length + tasks.projects.length).toString().padStart(2, '0') }), _jsxs(Text, { color: theme.colors.textMuted, children: [" FLOQ v", VERSION] }), _jsx(Text, { color: tursoEnabled ? theme.colors.accent : theme.colors.textMuted, children: tursoEnabled ? ' [TURSO]' : ' [LOCAL]' }), contextFilter !== null && (_jsxs(Text, { color: theme.colors.accent, children: [' ', "@", contextFilter === '' ? 'none' : contextFilter] })), focusFilter && (_jsxs(Text, { color: theme.colors.accent, children: [" ", i18n.tui.focus?.focused || '★ Focused'] }))] }), _jsxs(Box, { children: [_jsx(CalendarEvents, { compact: true, showLabel: true, withSeparator: true }), _jsx(Clock, {})] })] }), pomodoro.isRunning && pomodoro.state && (_jsx(Box, { marginBottom: 1, children: _jsx(PomodoroMarioUI, { state: pomodoro.state, remainingSeconds: pomodoro.remainingSeconds, isPaused: pomodoro.isPaused, score: tasks.done.length * 100, coins: pomodoro.state.completedCount, lives: Math.floor(tasks.done.length / 10) + 3, world: `${new Date().getMonth() + 1}-${new Date().getDate()}`, width: terminalWidth - 4, compact: true }) })), mode === 'context-filter' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.colors.secondary, bold: true, children: "Filter by context" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: ['all', 'none', ...availableContexts].map((ctx, index) => {
946
1068
  const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
947
1069
  return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
948
1070
  }) })] })) : 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) => {
949
1071
  const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
950
1072
  return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
951
- }) })] })) : 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(MarioBoxInline, { 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') })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(MarioBoxInline, { 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) => {
1073
+ }) })] })) : mode === 'set-effort' && currentTasks.length > 0 ? (_jsx(Box, { flexDirection: "column", children: _jsxs(MarioBoxInline, { title: i18n.tui.effort?.set || 'Set Effort', width: terminalWidth - 4, minHeight: 4, isActive: true, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.effort?.set || 'Set effort for', ": ", currentTasks[selectedTaskIndex]?.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: EFFORT_OPTIONS.map((opt, index) => (_jsxs(Text, { color: index === effortSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === effortSelectIndex, children: [index === effortSelectIndex ? '🍄 ' : ' ', opt.label] }, opt.value || 'clear'))) })] }) })) : 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(MarioBoxInline, { 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(MarioBoxInline, { 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) => {
952
1074
  const isSelected = index === selectedCommentIndex && mode === 'task-detail';
953
1075
  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));
954
1076
  })) }) })] })) : (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { marginRight: 2, children: _jsx(MarioBoxInline, { title: mode === 'project-detail' && selectedProject ? selectedProject.title : 'STAGE', 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) => {
@@ -959,6 +1081,8 @@ export function GtdMario({ onOpenSettings }) {
959
1081
  const isSelected = (paneFocus === 'tasks' || mode === 'project-detail') && index === selectedTaskIndex;
960
1082
  const parentProject = getParentProject(task.parentId);
961
1083
  const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
1084
+ const focusPrefix = task.isFocused ? '★ ' : '';
1085
+ const effortBadge = task.effort ? `[${{ 'small': 'S', 'medium': 'M', 'large': 'L' }[task.effort]}] ` : '';
962
1086
  const prefix = isSelected ? '🍄 ' : ' ';
963
1087
  const suffix = [
964
1088
  task.waitingFor ? ` (${task.waitingFor})` : '',
@@ -966,9 +1090,9 @@ export function GtdMario({ onOpenSettings }) {
966
1090
  parentProject ? ` [${parentProject.title}]` : '',
967
1091
  progress ? ` [${progress.completed}/${progress.total}]` : '',
968
1092
  ].join('');
969
- const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(suffix);
1093
+ const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(`${focusPrefix}${effortBadge}`) - getDisplayWidth(suffix);
970
1094
  const displayTitle = truncateString(task.title, availableWidth);
971
- return (_jsxs(Text, { color: isSelected ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [prefix, 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));
1095
+ 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));
972
1096
  })) }) })] })), (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
973
1097
  ? `${i18n.tui.newTask}[${selectedProject.title}] `
974
1098
  : 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'