floq 1.3.2 → 1.4.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.
- package/README.ja.md +8 -0
- package/README.md +8 -0
- package/dist/cli.js +10 -0
- package/dist/commands/insights.d.ts +1 -0
- package/dist/commands/insights.js +283 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +6 -0
- package/dist/db/index.js +24 -0
- package/dist/db/schema.d.ts +37 -0
- package/dist/db/schema.js +2 -0
- package/dist/i18n/en.d.ts +82 -0
- package/dist/i18n/en.js +57 -5
- package/dist/i18n/ja.js +57 -5
- package/dist/ui/App.js +110 -7
- package/dist/ui/components/GtdDQ.js +117 -9
- package/dist/ui/components/GtdMario.js +113 -8
- package/dist/ui/components/HelpModal.js +8 -0
- package/dist/ui/components/InsightsModal.d.ts +6 -0
- package/dist/ui/components/InsightsModal.js +272 -0
- package/dist/ui/components/KanbanBoard.js +119 -11
- package/dist/ui/components/KanbanColumn.js +7 -2
- package/dist/ui/components/KanbanDQ.js +120 -11
- package/dist/ui/components/KanbanMario.js +125 -11
- package/dist/ui/components/TaskItem.js +7 -2
- package/dist/ui/history/commands/SetEffortCommand.d.ts +20 -0
- package/dist/ui/history/commands/SetEffortCommand.js +37 -0
- package/dist/ui/history/commands/SetFocusCommand.d.ts +20 -0
- package/dist/ui/history/commands/SetFocusCommand.js +37 -0
- package/dist/ui/history/commands/index.d.ts +2 -0
- package/dist/ui/history/commands/index.js +2 -0
- package/dist/ui/history/index.d.ts +1 -1
- package/dist/ui/history/index.js +1 -1
- package/package.json +1 -1
|
@@ -7,11 +7,12 @@ 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';
|
|
16
17
|
import { Clock } from './Clock.js';
|
|
17
18
|
import { CalendarEvents } from './CalendarEvents.js';
|
|
@@ -155,6 +156,9 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
155
156
|
const [searchQuery, setSearchQuery] = useState('');
|
|
156
157
|
const [searchResults, setSearchResults] = useState([]);
|
|
157
158
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
159
|
+
// Focus filter state
|
|
160
|
+
const [focusFilterState, setFocusFilterState] = useState(getFocusFilter());
|
|
161
|
+
const [effortSelectIndex, setEffortSelectIndex] = useState(0);
|
|
158
162
|
// Pomodoro focus mode state
|
|
159
163
|
const [focusMode, setFocusModeState] = useState(() => getPomodoroFocusMode());
|
|
160
164
|
const toggleFocusMode = useCallback(() => {
|
|
@@ -181,6 +185,17 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
181
185
|
const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
|
|
182
186
|
const currentTab = TABS[currentTabIndex];
|
|
183
187
|
const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
|
|
188
|
+
const EFFORT_OPTIONS = [
|
|
189
|
+
{ value: 'small', label: i18n.tui.effort?.small || 'Small' },
|
|
190
|
+
{ value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
|
|
191
|
+
{ value: 'large', label: i18n.tui.effort?.large || 'Large' },
|
|
192
|
+
{ value: null, label: i18n.tui.effort?.clear || 'Clear' },
|
|
193
|
+
];
|
|
194
|
+
const getCurrentTask = () => {
|
|
195
|
+
if (currentTasks.length === 0)
|
|
196
|
+
return undefined;
|
|
197
|
+
return currentTasks[selectedTaskIndex];
|
|
198
|
+
};
|
|
184
199
|
const loadTasks = useCallback(async () => {
|
|
185
200
|
const db = getDb();
|
|
186
201
|
const newTasks = {
|
|
@@ -214,6 +229,9 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
214
229
|
allTasks = allTasks.filter(t => t.context === contextFilter);
|
|
215
230
|
}
|
|
216
231
|
}
|
|
232
|
+
if (focusFilterState) {
|
|
233
|
+
allTasks = allTasks.filter(t => t.isFocused);
|
|
234
|
+
}
|
|
217
235
|
newTasks[status] = allTasks;
|
|
218
236
|
}
|
|
219
237
|
newTasks.projects = await db
|
|
@@ -233,7 +251,7 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
233
251
|
setProjectProgress(progress);
|
|
234
252
|
setTasks(newTasks);
|
|
235
253
|
setAvailableContexts(getContexts());
|
|
236
|
-
}, [contextFilter]);
|
|
254
|
+
}, [contextFilter, focusFilterState]);
|
|
237
255
|
const loadProjectTasks = useCallback(async (projectId) => {
|
|
238
256
|
const db = getDb();
|
|
239
257
|
const children = await db
|
|
@@ -430,6 +448,48 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
430
448
|
setMessage(description);
|
|
431
449
|
await loadTasks();
|
|
432
450
|
}, [i18n.tui.context, loadTasks, history]);
|
|
451
|
+
const toggleTaskFocus = async () => {
|
|
452
|
+
const task = getCurrentTask();
|
|
453
|
+
if (!task)
|
|
454
|
+
return;
|
|
455
|
+
const newFocused = !task.isFocused;
|
|
456
|
+
const description = newFocused
|
|
457
|
+
? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
|
|
458
|
+
: fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
|
|
459
|
+
const command = new SetFocusCommand({
|
|
460
|
+
taskId: task.id,
|
|
461
|
+
fromFocused: task.isFocused,
|
|
462
|
+
toFocused: newFocused,
|
|
463
|
+
description,
|
|
464
|
+
});
|
|
465
|
+
await history.execute(command);
|
|
466
|
+
setMessage(description);
|
|
467
|
+
await loadTasks();
|
|
468
|
+
};
|
|
469
|
+
const toggleFocusFilter = () => {
|
|
470
|
+
const newValue = !focusFilterState;
|
|
471
|
+
setFocusFilterState(newValue);
|
|
472
|
+
setFocusFilter(newValue);
|
|
473
|
+
setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
|
|
474
|
+
};
|
|
475
|
+
const setTaskEffort = async (effort) => {
|
|
476
|
+
const task = getCurrentTask();
|
|
477
|
+
if (!task)
|
|
478
|
+
return;
|
|
479
|
+
const description = effort
|
|
480
|
+
? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
|
|
481
|
+
: fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
|
|
482
|
+
const command = new SetEffortCommand({
|
|
483
|
+
taskId: task.id,
|
|
484
|
+
fromEffort: task.effort,
|
|
485
|
+
toEffort: effort,
|
|
486
|
+
description,
|
|
487
|
+
});
|
|
488
|
+
await history.execute(command);
|
|
489
|
+
setMessage(description);
|
|
490
|
+
setMode('normal');
|
|
491
|
+
await loadTasks();
|
|
492
|
+
};
|
|
433
493
|
const handleInputSubmit = async (value) => {
|
|
434
494
|
// Handle search mode submit
|
|
435
495
|
if (mode === 'search') {
|
|
@@ -494,8 +554,8 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
494
554
|
}
|
|
495
555
|
};
|
|
496
556
|
useInput((input, key) => {
|
|
497
|
-
// Handle help mode - let
|
|
498
|
-
if (mode === 'help') {
|
|
557
|
+
// Handle help/insights mode - let modals handle their own input
|
|
558
|
+
if (mode === 'help' || mode === 'insights') {
|
|
499
559
|
return;
|
|
500
560
|
}
|
|
501
561
|
// Battle command navigation (j/k) - when timer is running and in focus mode
|
|
@@ -730,6 +790,26 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
730
790
|
}
|
|
731
791
|
return;
|
|
732
792
|
}
|
|
793
|
+
// Handle set-effort mode
|
|
794
|
+
if (mode === 'set-effort') {
|
|
795
|
+
if (key.escape) {
|
|
796
|
+
setMode('normal');
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (input === 'j' || key.downArrow) {
|
|
800
|
+
setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (input === 'k' || key.upArrow) {
|
|
804
|
+
setEffortSelectIndex(prev => Math.max(prev - 1, 0));
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (key.return) {
|
|
808
|
+
setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
733
813
|
// Handle select-project mode
|
|
734
814
|
if (mode === 'select-project') {
|
|
735
815
|
if (key.escape) {
|
|
@@ -801,6 +881,10 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
801
881
|
setMode('help');
|
|
802
882
|
return;
|
|
803
883
|
}
|
|
884
|
+
if (input === 'I') {
|
|
885
|
+
setMode('insights');
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
804
888
|
if (input === 'C') {
|
|
805
889
|
setMode('calendar');
|
|
806
890
|
return;
|
|
@@ -958,6 +1042,24 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
958
1042
|
setMode('set-context');
|
|
959
1043
|
return;
|
|
960
1044
|
}
|
|
1045
|
+
// Toggle focus on task
|
|
1046
|
+
if (input === 'g') {
|
|
1047
|
+
toggleTaskFocus();
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
// Toggle focus filter
|
|
1051
|
+
if (input === 'G') {
|
|
1052
|
+
toggleFocusFilter();
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
// Set effort
|
|
1056
|
+
if (input === 'E') {
|
|
1057
|
+
if (getCurrentTask()) {
|
|
1058
|
+
setEffortSelectIndex(0);
|
|
1059
|
+
setMode('set-effort');
|
|
1060
|
+
}
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
961
1063
|
}
|
|
962
1064
|
// Undo
|
|
963
1065
|
if (input === 'u') {
|
|
@@ -1035,6 +1137,10 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1035
1137
|
if (mode === 'help') {
|
|
1036
1138
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
|
|
1037
1139
|
}
|
|
1140
|
+
// Insights modal overlay
|
|
1141
|
+
if (mode === 'insights') {
|
|
1142
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
|
|
1143
|
+
}
|
|
1038
1144
|
// Calendar modal overlay
|
|
1039
1145
|
if (mode === 'calendar') {
|
|
1040
1146
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
|
|
@@ -1045,13 +1151,13 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1045
1151
|
const battleWidth = terminalWidth - 4;
|
|
1046
1152
|
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
1153
|
}
|
|
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) => {
|
|
1154
|
+
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
1155
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
1050
1156
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
|
|
1051
1157
|
}) })] })) : 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
1158
|
const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
|
|
1053
1159
|
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) => {
|
|
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) => {
|
|
1055
1161
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
1056
1162
|
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
1163
|
})) }) })] })) : (_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 +1168,8 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1062
1168
|
const isSelected = (paneFocus === 'tasks' || mode === 'project-detail') && index === selectedTaskIndex;
|
|
1063
1169
|
const parentProject = getParentProject(task.parentId);
|
|
1064
1170
|
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
1171
|
+
const focusPrefix = task.isFocused ? '★ ' : '';
|
|
1172
|
+
const effortBadge = task.effort ? `[${{ 'small': 'S', 'medium': 'M', 'large': 'L' }[task.effort]}] ` : '';
|
|
1065
1173
|
// Calculate available width for title
|
|
1066
1174
|
const prefix = isSelected ? '▶ ' : ' ';
|
|
1067
1175
|
const suffix = [
|
|
@@ -1070,9 +1178,9 @@ export function GtdDQ({ onOpenSettings }) {
|
|
|
1070
1178
|
parentProject ? ` [${parentProject.title}]` : '',
|
|
1071
1179
|
progress ? ` [${progress.completed}/${progress.total}]` : '',
|
|
1072
1180
|
].join('');
|
|
1073
|
-
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(suffix);
|
|
1181
|
+
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(focusPrefix) - getDisplayWidth(effortBadge) - getDisplayWidth(suffix);
|
|
1074
1182
|
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));
|
|
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));
|
|
1076
1184
|
})) }) })] })), (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
1185
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
1078
1186
|
: 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'
|
|
@@ -7,12 +7,13 @@ 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';
|
|
17
18
|
import { MarioBoxInline } from './MarioBox.js';
|
|
18
19
|
import { Clock } from './Clock.js';
|
|
@@ -97,6 +98,8 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
97
98
|
const [searchQuery, setSearchQuery] = useState('');
|
|
98
99
|
const [searchResults, setSearchResults] = useState([]);
|
|
99
100
|
const [searchResultIndex, setSearchResultIndex] = useState(0);
|
|
101
|
+
const [focusFilter, setFocusFilterState] = useState(getFocusFilter());
|
|
102
|
+
const [effortSelectIndex, setEffortSelectIndex] = useState(0);
|
|
100
103
|
// Focus mode state (can be toggled during pomodoro)
|
|
101
104
|
const [focusMode, setFocusModeState] = useState(() => getPomodoroFocusMode());
|
|
102
105
|
const toggleFocusMode = useCallback(() => {
|
|
@@ -132,6 +135,17 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
132
135
|
const rightPaneWidth = terminalWidth - leftPaneWidth - 6;
|
|
133
136
|
const currentTab = TABS[currentTabIndex];
|
|
134
137
|
const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
|
|
138
|
+
const getCurrentTask = () => {
|
|
139
|
+
if (currentTasks.length === 0)
|
|
140
|
+
return undefined;
|
|
141
|
+
return currentTasks[selectedTaskIndex];
|
|
142
|
+
};
|
|
143
|
+
const EFFORT_OPTIONS = [
|
|
144
|
+
{ value: 'small', label: i18n.tui.effort?.small || 'Small' },
|
|
145
|
+
{ value: 'medium', label: i18n.tui.effort?.medium || 'Medium' },
|
|
146
|
+
{ value: 'large', label: i18n.tui.effort?.large || 'Large' },
|
|
147
|
+
{ value: null, label: i18n.tui.effort?.clear || 'Clear' },
|
|
148
|
+
];
|
|
135
149
|
const loadTasks = useCallback(async () => {
|
|
136
150
|
const db = getDb();
|
|
137
151
|
const newTasks = {
|
|
@@ -165,6 +179,9 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
165
179
|
allTasks = allTasks.filter(t => t.context === contextFilter);
|
|
166
180
|
}
|
|
167
181
|
}
|
|
182
|
+
if (focusFilter) {
|
|
183
|
+
allTasks = allTasks.filter(t => t.isFocused);
|
|
184
|
+
}
|
|
168
185
|
newTasks[status] = allTasks;
|
|
169
186
|
}
|
|
170
187
|
newTasks.projects = await db
|
|
@@ -184,7 +201,7 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
184
201
|
setProjectProgress(progress);
|
|
185
202
|
setTasks(newTasks);
|
|
186
203
|
setAvailableContexts(getContexts());
|
|
187
|
-
}, [contextFilter]);
|
|
204
|
+
}, [contextFilter, focusFilter]);
|
|
188
205
|
const loadProjectTasks = useCallback(async (projectId) => {
|
|
189
206
|
const db = getDb();
|
|
190
207
|
const children = await db
|
|
@@ -377,6 +394,48 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
377
394
|
setMessage(description);
|
|
378
395
|
await loadTasks();
|
|
379
396
|
}, [i18n.tui.context, loadTasks, history]);
|
|
397
|
+
const toggleTaskFocus = async () => {
|
|
398
|
+
const task = getCurrentTask();
|
|
399
|
+
if (!task)
|
|
400
|
+
return;
|
|
401
|
+
const newFocused = !task.isFocused;
|
|
402
|
+
const description = newFocused
|
|
403
|
+
? fmt(i18n.tui.focus?.taskFocused || 'Focused: "{title}"', { title: task.title })
|
|
404
|
+
: fmt(i18n.tui.focus?.taskUnfocused || 'Unfocused: "{title}"', { title: task.title });
|
|
405
|
+
const command = new SetFocusCommand({
|
|
406
|
+
taskId: task.id,
|
|
407
|
+
fromFocused: task.isFocused,
|
|
408
|
+
toFocused: newFocused,
|
|
409
|
+
description,
|
|
410
|
+
});
|
|
411
|
+
await history.execute(command);
|
|
412
|
+
setMessage(description);
|
|
413
|
+
await loadTasks();
|
|
414
|
+
};
|
|
415
|
+
const toggleFocusFilter = () => {
|
|
416
|
+
const newValue = !focusFilter;
|
|
417
|
+
setFocusFilterState(newValue);
|
|
418
|
+
setFocusFilter(newValue);
|
|
419
|
+
setMessage(newValue ? (i18n.tui.focus?.filterOn || 'Focus filter ON') : (i18n.tui.focus?.filterOff || 'Focus filter OFF'));
|
|
420
|
+
};
|
|
421
|
+
const setTaskEffort = async (effort) => {
|
|
422
|
+
const task = getCurrentTask();
|
|
423
|
+
if (!task)
|
|
424
|
+
return;
|
|
425
|
+
const description = effort
|
|
426
|
+
? fmt(i18n.tui.effort?.effortSet || 'Set effort {effort} for "{title}"', { effort: i18n.tui.effort?.[effort] || effort, title: task.title })
|
|
427
|
+
: fmt(i18n.tui.effort?.effortCleared || 'Cleared effort for "{title}"', { title: task.title });
|
|
428
|
+
const command = new SetEffortCommand({
|
|
429
|
+
taskId: task.id,
|
|
430
|
+
fromEffort: task.effort,
|
|
431
|
+
toEffort: effort,
|
|
432
|
+
description,
|
|
433
|
+
});
|
|
434
|
+
await history.execute(command);
|
|
435
|
+
setMessage(description);
|
|
436
|
+
setMode('normal');
|
|
437
|
+
await loadTasks();
|
|
438
|
+
};
|
|
380
439
|
const handleInputSubmit = async (value) => {
|
|
381
440
|
if (mode === 'search') {
|
|
382
441
|
if (searchResults.length > 0) {
|
|
@@ -439,7 +498,7 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
439
498
|
}
|
|
440
499
|
};
|
|
441
500
|
useInput((input, key) => {
|
|
442
|
-
if (mode === 'help') {
|
|
501
|
+
if (mode === 'help' || mode === 'insights') {
|
|
443
502
|
return;
|
|
444
503
|
}
|
|
445
504
|
// Focus mode key handling - simplified controls
|
|
@@ -644,6 +703,25 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
644
703
|
}
|
|
645
704
|
return;
|
|
646
705
|
}
|
|
706
|
+
if (mode === 'set-effort') {
|
|
707
|
+
if (key.escape) {
|
|
708
|
+
setMode('normal');
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (input === 'j' || key.downArrow) {
|
|
712
|
+
setEffortSelectIndex(prev => Math.min(prev + 1, EFFORT_OPTIONS.length - 1));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (input === 'k' || key.upArrow) {
|
|
716
|
+
setEffortSelectIndex(prev => Math.max(prev - 1, 0));
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (key.return) {
|
|
720
|
+
setTaskEffort(EFFORT_OPTIONS[effortSelectIndex].value);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
647
725
|
if (mode === 'select-project') {
|
|
648
726
|
if (key.escape) {
|
|
649
727
|
const wasFromTaskDetail = selectedTask !== null;
|
|
@@ -712,6 +790,10 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
712
790
|
setMode('help');
|
|
713
791
|
return;
|
|
714
792
|
}
|
|
793
|
+
if (input === 'I') {
|
|
794
|
+
setMode('insights');
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
715
797
|
if (input === 'C') {
|
|
716
798
|
setMode('calendar');
|
|
717
799
|
return;
|
|
@@ -857,6 +939,24 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
857
939
|
setMode('set-context');
|
|
858
940
|
return;
|
|
859
941
|
}
|
|
942
|
+
// Toggle focus on task
|
|
943
|
+
if (input === 'g') {
|
|
944
|
+
toggleTaskFocus();
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
// Toggle focus filter
|
|
948
|
+
if (input === 'G') {
|
|
949
|
+
toggleFocusFilter();
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
// Set effort
|
|
953
|
+
if (input === 'E') {
|
|
954
|
+
if (getCurrentTask()) {
|
|
955
|
+
setEffortSelectIndex(0);
|
|
956
|
+
setMode('set-effort');
|
|
957
|
+
}
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
860
960
|
}
|
|
861
961
|
if (input === 'u') {
|
|
862
962
|
history.undo().then((didUndo) => {
|
|
@@ -928,6 +1028,9 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
928
1028
|
if (mode === 'help') {
|
|
929
1029
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
|
|
930
1030
|
}
|
|
1031
|
+
if (mode === 'insights') {
|
|
1032
|
+
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(InsightsModal, { onClose: () => setMode('normal') }) }));
|
|
1033
|
+
}
|
|
931
1034
|
if (mode === 'calendar') {
|
|
932
1035
|
return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(CalendarModal, { onClose: () => setMode('normal') }) }));
|
|
933
1036
|
}
|
|
@@ -942,13 +1045,13 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
942
1045
|
const marioWidth = terminalWidth - 4;
|
|
943
1046
|
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
1047
|
}
|
|
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) => {
|
|
1048
|
+
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
1049
|
const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
|
|
947
1050
|
return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '🍄 ' : ' ', label] }, ctx));
|
|
948
1051
|
}) })] })) : 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
1052
|
const label = ctx === 'clear' ? (i18n.tui.context?.none || 'Clear context') : ctx === 'new' ? (i18n.tui.context?.addNew || 'New context...') : `@${ctx}`;
|
|
950
1053
|
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) => {
|
|
1054
|
+
}) })] })) : 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
1055
|
const isSelected = index === selectedCommentIndex && mode === 'task-detail';
|
|
953
1056
|
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
1057
|
})) }) })] })) : (_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 +1062,8 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
959
1062
|
const isSelected = (paneFocus === 'tasks' || mode === 'project-detail') && index === selectedTaskIndex;
|
|
960
1063
|
const parentProject = getParentProject(task.parentId);
|
|
961
1064
|
const progress = currentTab === 'projects' ? projectProgress[task.id] : undefined;
|
|
1065
|
+
const focusPrefix = task.isFocused ? '★ ' : '';
|
|
1066
|
+
const effortBadge = task.effort ? `[${{ 'small': 'S', 'medium': 'M', 'large': 'L' }[task.effort]}] ` : '';
|
|
962
1067
|
const prefix = isSelected ? '🍄 ' : ' ';
|
|
963
1068
|
const suffix = [
|
|
964
1069
|
task.waitingFor ? ` (${task.waitingFor})` : '',
|
|
@@ -966,9 +1071,9 @@ export function GtdMario({ onOpenSettings }) {
|
|
|
966
1071
|
parentProject ? ` [${parentProject.title}]` : '',
|
|
967
1072
|
progress ? ` [${progress.completed}/${progress.total}]` : '',
|
|
968
1073
|
].join('');
|
|
969
|
-
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(suffix);
|
|
1074
|
+
const availableWidth = rightPaneWidth - 6 - getDisplayWidth(prefix) - getDisplayWidth(`${focusPrefix}${effortBadge}`) - getDisplayWidth(suffix);
|
|
970
1075
|
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));
|
|
1076
|
+
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));
|
|
972
1077
|
})) }) })] })), (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
1078
|
? `${i18n.tui.newTask}[${selectedProject.title}] `
|
|
974
1079
|
: 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'
|
|
@@ -28,6 +28,9 @@ export function HelpModal({ onClose, isKanban = false }) {
|
|
|
28
28
|
{ type: 'key', key: 'd', value: kHelp.completeTask },
|
|
29
29
|
{ type: 'key', key: 'm', value: kHelp.moveRight },
|
|
30
30
|
{ type: 'key', key: 'BS', value: kHelp.moveLeft },
|
|
31
|
+
{ type: 'key', key: 'g', value: kHelp.toggleFocus || 'Toggle focus' },
|
|
32
|
+
{ type: 'key', key: 'G', value: kHelp.toggleFocusFilter || 'Toggle focus filter' },
|
|
33
|
+
{ type: 'key', key: 'E', value: kHelp.setEffort || 'Set effort size' },
|
|
31
34
|
{ type: 'key', key: 'u', value: 'Undo' },
|
|
32
35
|
{ type: 'key', key: 'Ctrl+r', value: 'Redo' },
|
|
33
36
|
{ type: 'header', value: kHelp.settings },
|
|
@@ -36,6 +39,7 @@ export function HelpModal({ onClose, isKanban = false }) {
|
|
|
36
39
|
{ type: 'key', key: 'L', value: kHelp.changeLanguage },
|
|
37
40
|
{ type: 'header', value: kHelp.other },
|
|
38
41
|
{ type: 'key', key: '/', value: kHelp.searchTasks },
|
|
42
|
+
{ type: 'key', key: 'I', value: help.showInsights || 'Show insights' },
|
|
39
43
|
{ type: 'key', key: '?', value: kHelp.showHelp },
|
|
40
44
|
{ type: 'key', key: 'q', value: kHelp.quit },
|
|
41
45
|
];
|
|
@@ -53,6 +57,9 @@ export function HelpModal({ onClose, isKanban = false }) {
|
|
|
53
57
|
{ type: 'key', key: 'w', value: help.moveToWaiting },
|
|
54
58
|
{ type: 'key', key: 'i', value: help.moveToInbox },
|
|
55
59
|
{ type: 'key', key: 'r', value: help.refresh },
|
|
60
|
+
{ type: 'key', key: 'g', value: help.toggleFocus || 'Toggle focus' },
|
|
61
|
+
{ type: 'key', key: 'G', value: help.toggleFocusFilter || 'Toggle focus filter' },
|
|
62
|
+
{ type: 'key', key: 'E', value: help.setEffort || 'Set effort size' },
|
|
56
63
|
{ type: 'key', key: 'u', value: help.undo },
|
|
57
64
|
{ type: 'key', key: 'Ctrl+r', value: help.redo },
|
|
58
65
|
{ type: 'header', value: help.projects },
|
|
@@ -72,6 +79,7 @@ export function HelpModal({ onClose, isKanban = false }) {
|
|
|
72
79
|
{ type: 'header', value: help.other },
|
|
73
80
|
{ type: 'key', key: '/', value: help.searchTasks },
|
|
74
81
|
{ type: 'key', key: 'C', value: help.showCalendar || 'Show calendar' },
|
|
82
|
+
{ type: 'key', key: 'I', value: help.showInsights || 'Show insights' },
|
|
75
83
|
{ type: 'key', key: '?', value: help.showHelp },
|
|
76
84
|
{ type: 'key', key: 'q', value: help.quit },
|
|
77
85
|
];
|