floq 0.6.0 → 0.7.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/dist/i18n/en.d.ts CHANGED
@@ -85,6 +85,16 @@ export declare const en: {
85
85
  waitingFor: string;
86
86
  refreshed: string;
87
87
  footer: string;
88
+ dqFooter: {
89
+ tabs: string;
90
+ tasks: string;
91
+ projectDetail: string;
92
+ taskDetail: string;
93
+ };
94
+ kanbanFooter: {
95
+ category: string;
96
+ tasks: string;
97
+ };
88
98
  noTasks: string;
89
99
  tabInbox: string;
90
100
  tabNext: string;
@@ -435,6 +445,16 @@ export type TuiTranslations = {
435
445
  waitingFor: string;
436
446
  refreshed: string;
437
447
  footer: string;
448
+ dqFooter: {
449
+ tabs: string;
450
+ tasks: string;
451
+ projectDetail: string;
452
+ taskDetail: string;
453
+ };
454
+ kanbanFooter: {
455
+ category: string;
456
+ tasks: string;
457
+ };
438
458
  noTasks: string;
439
459
  tabInbox: string;
440
460
  tabNext: string;
package/dist/i18n/en.js CHANGED
@@ -89,7 +89,18 @@ export const en = {
89
89
  movedToWaiting: 'Moved "{title}" to Waiting (for {person})',
90
90
  waitingFor: 'Waiting for: ',
91
91
  refreshed: 'Refreshed',
92
- footer: 'a=add d=done D=delete n=next s=someday w=waiting i=inbox p=project P=link',
92
+ footer: 'a=add d=done D=delete n=next s=someday w=waiting i=inbox p=project P=link c=context',
93
+ // DQ/Mario style footers
94
+ dqFooter: {
95
+ tabs: 'j/k=select l/Enter=tasks 1-6=tab a=add @=filter /=search',
96
+ tasks: 'j/k=select Enter=detail h/Esc=back d=done n=next s=someday w=wait i=inbox c=context p=project P=link D=delete u=undo /=search',
97
+ projectDetail: 'j/k=select a=add d=done Esc/b=back /=search',
98
+ taskDetail: 'j/k=select c/i=add comment P=link D=delete comment Esc/b=back',
99
+ },
100
+ kanbanFooter: {
101
+ category: 'j/k=select l/Enter=tasks a=add @=filter /=search',
102
+ tasks: 'j/k=select h/Esc=back d=done m=move a=add u=undo /=search',
103
+ },
93
104
  noTasks: 'No tasks',
94
105
  // Tab labels
95
106
  tabInbox: 'Inbox',
package/dist/i18n/ja.js CHANGED
@@ -89,7 +89,18 @@ export const ja = {
89
89
  movedToWaiting: '「{title}」を連絡待ち({person})に移動しました',
90
90
  waitingFor: '待機相手: ',
91
91
  refreshed: '更新しました',
92
- footer: 'a=追加 d=完了 D=削除 n=次 s=いつか w=待ち i=Inbox p=プロジェクト化 P=紐づけ',
92
+ footer: 'a=追加 d=完了 D=削除 n=次 s=いつか w=待ち i=Inbox p=プロジェクト化 P=紐づけ c=コンテキスト',
93
+ // DQ/Mario style footers
94
+ dqFooter: {
95
+ tabs: 'j/k=選択 l/Enter=タスク 1-6=タブ a=追加 @=フィルター /=検索',
96
+ tasks: 'j/k=選択 Enter=詳細 h/Esc=戻る d=完了 n=次 s=いつか w=待ち i=inbox c=コンテキスト p=プロジェクト化 P=紐づけ D=削除 u=戻す /=検索',
97
+ projectDetail: 'j/k=選択 a=追加 d=完了 Esc/b=戻る /=検索',
98
+ taskDetail: 'j/k=選択 c/i=コメント追加 P=紐づけ D=コメント削除 Esc/b=戻る',
99
+ },
100
+ kanbanFooter: {
101
+ category: 'j/k=選択 l/Enter=タスク a=追加 @=フィルター /=検索',
102
+ tasks: 'j/k=選択 h/Esc=戻る d=完了 m=移動 a=追加 u=戻す /=検索',
103
+ },
93
104
  noTasks: 'タスクなし',
94
105
  // Tab labels
95
106
  tabInbox: 'Inbox',
package/dist/ui/App.js CHANGED
@@ -20,7 +20,9 @@ import { ThemeProvider, useTheme, getTheme } from './theme/index.js';
20
20
  import { getThemeName, getViewMode, setThemeName, setViewMode, setLocale, isTursoEnabled, getContexts, addContext, getSplashDuration } from '../config.js';
21
21
  import { KanbanBoard } from './components/KanbanBoard.js';
22
22
  import { KanbanDQ } from './components/KanbanDQ.js';
23
+ import { KanbanMario } from './components/KanbanMario.js';
23
24
  import { GtdDQ } from './components/GtdDQ.js';
25
+ import { GtdMario } from './components/GtdMario.js';
24
26
  import { VERSION } from '../version.js';
25
27
  import { HistoryProvider, useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from './history/index.js';
26
28
  const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
@@ -51,6 +53,7 @@ export function App() {
51
53
  };
52
54
  const currentTheme = getTheme(themeName);
53
55
  const useDQStyle = currentTheme.uiStyle === 'titled-box';
56
+ const useMarioStyle = currentTheme.uiStyle === 'mario-block';
54
57
  // Show splash screen (all themes, configurable duration)
55
58
  if (showSplash) {
56
59
  return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(SplashScreen, { onComplete: () => setShowSplash(false), duration: splashDuration, viewMode: viewMode }) }));
@@ -65,7 +68,7 @@ export function App() {
65
68
  if (settingsMode === 'lang-select') {
66
69
  return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(LanguageSelector, { onSelect: handleLocaleSelect, onCancel: handleSettingsCancel }) }));
67
70
  }
68
- return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(HistoryProvider, { children: viewMode === 'kanban' ? (useDQStyle ? (_jsx(KanbanDQ, { onOpenSettings: setSettingsMode })) : (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode }))) : (useDQStyle ? (_jsx(GtdDQ, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode }))) }) }));
71
+ return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(HistoryProvider, { children: viewMode === 'kanban' ? (useMarioStyle ? (_jsx(KanbanMario, { onOpenSettings: setSettingsMode })) : useDQStyle ? (_jsx(KanbanDQ, { onOpenSettings: setSettingsMode })) : (_jsx(KanbanBoard, { onOpenSettings: setSettingsMode }))) : (useMarioStyle ? (_jsx(GtdMario, { onOpenSettings: setSettingsMode })) : useDQStyle ? (_jsx(GtdDQ, { onOpenSettings: setSettingsMode })) : (_jsx(AppContent, { onOpenSettings: setSettingsMode }))) }) }));
69
72
  }
70
73
  function AppContent({ onOpenSettings }) {
71
74
  const theme = useTheme();
@@ -52,6 +52,40 @@ const DQ_BORDER = {
52
52
  vertical: '│',
53
53
  };
54
54
  const TAGLINE = 'Flow your tasks, clear your mind';
55
+ // Mario/Nintendo style splash - SFC boot screen inspired
56
+ const MARIO_LOGO = [
57
+ ' ★ ★ ★ ★ ★ ★ ★ ★ ★',
58
+ '',
59
+ ' ████████ ██ ██████ ██████',
60
+ ' ██ ██ ██ ██ ██ ██',
61
+ ' ██████ ██ ██ ██ ██ ██',
62
+ ' ██ ██ ██ ██ ██ ▄▄ ██',
63
+ ' ██ ███████ ██████ ██████',
64
+ '',
65
+ ' ★ ★ ★ ★ ★ ★ ★ ★ ★',
66
+ ];
67
+ const MARIO_QUOTES_JA = [
68
+ 'イッツァミー!タスクマネージャー!',
69
+ 'マンマミーア!タスクがいっぱいだ!',
70
+ 'レッツァゴー!',
71
+ 'ヤッフー!',
72
+ 'スーパータスククリア!',
73
+ 'コインをゲット!',
74
+ '1UPキノコ!',
75
+ 'ファイアフラワー!',
76
+ 'スターパワー!',
77
+ ];
78
+ const MARIO_QUOTES_EN = [
79
+ "It's-a me! Task Manager!",
80
+ 'Mamma mia! So many tasks!',
81
+ "Let's-a go!",
82
+ 'Yahoo!',
83
+ 'Super Task Clear!',
84
+ 'Got a coin!',
85
+ '1-UP Mushroom!',
86
+ 'Fire Flower!',
87
+ 'Star Power!',
88
+ ];
55
89
  // Dragon Quest famous quotes for splash screen
56
90
  const DQ_QUOTES_JA = [
57
91
  'へんじがない。ただのしかばねのようだ。',
@@ -84,12 +118,17 @@ export function SplashScreen({ onComplete, duration = 1500, viewMode = 'gtd' })
84
118
  const theme = useTheme();
85
119
  const i18n = t();
86
120
  const isDqStyle = theme.uiStyle === 'titled-box';
87
- const isDosStyle = theme.name !== 'modern';
121
+ const isMarioStyle = theme.uiStyle === 'mario-block';
122
+ const isDosStyle = theme.name !== 'modern' && !isMarioStyle;
88
123
  const logo = isDosStyle ? LOGO_DOS : LOGO_MODERN;
89
124
  const [filled, empty] = theme.style.loadingChars;
90
125
  // Pick a random quote (stable across re-renders)
91
126
  const [randomQuote] = useState(() => {
92
127
  const isJapanese = i18n.splash?.welcome === 'ようこそ!';
128
+ if (isMarioStyle) {
129
+ const quotes = isJapanese ? MARIO_QUOTES_JA : MARIO_QUOTES_EN;
130
+ return quotes[Math.floor(Math.random() * quotes.length)];
131
+ }
93
132
  const quotes = isJapanese ? DQ_QUOTES_JA : DQ_QUOTES_EN;
94
133
  return quotes[Math.floor(Math.random() * quotes.length)];
95
134
  });
@@ -118,9 +157,9 @@ export function SplashScreen({ onComplete, duration = 1500, viewMode = 'gtd' })
118
157
  const taglineTimer = setTimeout(() => {
119
158
  setShowTagline(true);
120
159
  }, 600);
121
- // Blink effect for DQ style or wait-for-key mode
160
+ // Blink effect for DQ style, Mario style, or wait-for-key mode
122
161
  let blinkInterval = null;
123
- if (isDqStyle || waitForKeyPress) {
162
+ if (isDqStyle || isMarioStyle || waitForKeyPress) {
124
163
  blinkInterval = setInterval(() => {
125
164
  setBlinkVisible((prev) => !prev);
126
165
  }, 500);
@@ -140,7 +179,11 @@ export function SplashScreen({ onComplete, duration = 1500, viewMode = 'gtd' })
140
179
  if (blinkInterval)
141
180
  clearInterval(blinkInterval);
142
181
  };
143
- }, [onComplete, duration, isDqStyle, waitForKeyPress]);
182
+ }, [onComplete, duration, isDqStyle, isMarioStyle, waitForKeyPress]);
183
+ // Mario / Nintendo style splash (SFC boot screen inspired)
184
+ if (isMarioStyle) {
185
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.colors.secondary, children: "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501" }) }), _jsx(Box, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: MARIO_LOGO.map((line, index) => (_jsx(Text, { color: index === 0 || index === 8 ? theme.colors.secondary : theme.colors.primary, bold: true, children: line }, index))) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.colors.text, bold: true, children: "\uFF5E SUPER TASK MANAGER \uFF5E" }) }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: theme.colors.accent, children: ["\uD83C\uDF44 ", randomQuote] }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.colors.secondary, children: "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501" }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: blinkVisible ? '- PRESS START -' : ' ' }) }), _jsxs(Box, { marginTop: 2, flexDirection: "column", alignItems: "center", children: [_jsxs(Text, { color: theme.colors.textMuted, children: ["VER ", VERSION] }), _jsx(Text, { color: theme.colors.textMuted, children: "\u00A9 2026 polidog/PartyHard Inc." })] })] }));
186
+ }
144
187
  // Dragon Quest style splash
145
188
  if (isDqStyle) {
146
189
  const boxWidth = 40;
@@ -7,13 +7,41 @@ 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 } from '../../config.js';
11
- import { VERSION } from '../../version.js';
12
- import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, } from '../history/index.js';
10
+ import { isTursoEnabled, getContexts, addContext, getLocale } from '../../config.js';
11
+ import { useHistory, CreateTaskCommand, DeleteTaskCommand, MoveTaskCommand, LinkTaskCommand, ConvertToProjectCommand, CreateCommentCommand, DeleteCommentCommand, SetContextCommand, } from '../history/index.js';
13
12
  import { SearchBar } from './SearchBar.js';
14
13
  import { SearchResults } from './SearchResults.js';
15
14
  import { HelpModal } from './HelpModal.js';
16
15
  const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects', 'done'];
16
+ // Dragon Quest job classes
17
+ const DQ_JOBS_JA = [
18
+ 'ゆうしゃ',
19
+ 'せんし',
20
+ 'まほうつかい',
21
+ 'そうりょ',
22
+ 'ぶとうか',
23
+ 'とうぞく',
24
+ 'あそびにん',
25
+ 'けんじゃ',
26
+ 'バトルマスター',
27
+ 'パラディン',
28
+ 'レンジャー',
29
+ 'スーパースター',
30
+ ];
31
+ const DQ_JOBS_EN = [
32
+ 'Hero',
33
+ 'Warrior',
34
+ 'Mage',
35
+ 'Priest',
36
+ 'Martial Artist',
37
+ 'Thief',
38
+ 'Gadabout',
39
+ 'Sage',
40
+ 'Battle Master',
41
+ 'Paladin',
42
+ 'Ranger',
43
+ 'Superstar',
44
+ ];
17
45
  // Round border characters
18
46
  const BORDER = {
19
47
  topLeft: '╭',
@@ -81,6 +109,12 @@ export function GtdDQ({ onOpenSettings }) {
81
109
  const { stdout } = useStdout();
82
110
  const history = useHistory();
83
111
  const i18n = t();
112
+ // Random job class (stable across re-renders)
113
+ const [jobClass] = useState(() => {
114
+ const isJapanese = getLocale() === 'ja';
115
+ const jobs = isJapanese ? DQ_JOBS_JA : DQ_JOBS_EN;
116
+ return jobs[Math.floor(Math.random() * jobs.length)];
117
+ });
84
118
  const [mode, setMode] = useState('normal');
85
119
  const [paneFocus, setPaneFocus] = useState('tabs');
86
120
  const [currentTabIndex, setCurrentTabIndex] = useState(0);
@@ -325,6 +359,46 @@ export function GtdDQ({ onOpenSettings }) {
325
359
  setMessage(fmt(i18n.tui.linkedToProject || 'Linked "{title}" to {project}', { title: task.title, project: project.title }));
326
360
  await loadTasks();
327
361
  }, [i18n.tui.linkedToProject, loadTasks, history]);
362
+ const addCommentToTask = useCallback(async (task, content) => {
363
+ const commentId = uuidv4();
364
+ const command = new CreateCommentCommand({
365
+ comment: {
366
+ id: commentId,
367
+ taskId: task.id,
368
+ content: content.trim(),
369
+ createdAt: new Date(),
370
+ },
371
+ description: i18n.tui.commentAdded || 'Comment added',
372
+ });
373
+ await history.execute(command);
374
+ setMessage(i18n.tui.commentAdded || 'Comment added');
375
+ await loadTaskComments(task.id);
376
+ }, [history, i18n.tui.commentAdded, loadTaskComments]);
377
+ const deleteComment = useCallback(async (comment) => {
378
+ const command = new DeleteCommentCommand({
379
+ comment,
380
+ description: i18n.tui.commentDeleted || 'Comment deleted',
381
+ });
382
+ await history.execute(command);
383
+ setMessage(i18n.tui.commentDeleted || 'Comment deleted');
384
+ if (selectedTask) {
385
+ await loadTaskComments(selectedTask.id);
386
+ }
387
+ }, [i18n.tui.commentDeleted, loadTaskComments, selectedTask, history]);
388
+ const setTaskContext = useCallback(async (task, context) => {
389
+ const description = context
390
+ ? fmt(i18n.tui.context?.contextSet || 'Set context @{context} for "{title}"', { context, title: task.title })
391
+ : fmt(i18n.tui.context?.contextCleared || 'Cleared context for "{title}"', { title: task.title });
392
+ const command = new SetContextCommand({
393
+ taskId: task.id,
394
+ fromContext: task.context,
395
+ toContext: context,
396
+ description,
397
+ });
398
+ await history.execute(command);
399
+ setMessage(description);
400
+ await loadTasks();
401
+ }, [i18n.tui.context, loadTasks, history]);
328
402
  const handleInputSubmit = async (value) => {
329
403
  // Handle search mode submit
330
404
  if (mode === 'search') {
@@ -340,6 +414,15 @@ export function GtdDQ({ onOpenSettings }) {
340
414
  setSearchResultIndex(0);
341
415
  return;
342
416
  }
417
+ // Handle add-comment mode
418
+ if (mode === 'add-comment' && selectedTask) {
419
+ if (value.trim()) {
420
+ await addCommentToTask(selectedTask, value);
421
+ }
422
+ setInputValue('');
423
+ setMode('task-detail');
424
+ return;
425
+ }
343
426
  if (mode === 'move-to-waiting' && taskToWaiting) {
344
427
  if (value.trim()) {
345
428
  await moveTaskToWaiting(taskToWaiting, value);
@@ -394,12 +477,57 @@ export function GtdDQ({ onOpenSettings }) {
394
477
  else if (mode === 'add-context') {
395
478
  setMode('set-context');
396
479
  }
480
+ else if (mode === 'add-comment') {
481
+ setMode('task-detail');
482
+ }
397
483
  else {
398
484
  setMode('normal');
399
485
  }
400
486
  }
401
487
  return;
402
488
  }
489
+ // Handle task-detail mode
490
+ if (mode === 'task-detail') {
491
+ if (key.escape || input === 'b' || input === 'h' || key.leftArrow) {
492
+ setSelectedTask(null);
493
+ setTaskComments([]);
494
+ setSelectedCommentIndex(0);
495
+ setMode('normal');
496
+ return;
497
+ }
498
+ // Navigate comments
499
+ if (key.upArrow || input === 'k') {
500
+ setSelectedCommentIndex((prev) => (prev > 0 ? prev - 1 : Math.max(0, taskComments.length - 1)));
501
+ return;
502
+ }
503
+ if (key.downArrow || input === 'j') {
504
+ setSelectedCommentIndex((prev) => (prev < taskComments.length - 1 ? prev + 1 : 0));
505
+ return;
506
+ }
507
+ // Add comment
508
+ if (input === 'c' || input === 'i') {
509
+ setMode('add-comment');
510
+ return;
511
+ }
512
+ // Delete comment
513
+ if (input === 'D' && taskComments.length > 0) {
514
+ const comment = taskComments[selectedCommentIndex];
515
+ deleteComment(comment).then(() => {
516
+ if (selectedCommentIndex >= taskComments.length - 1) {
517
+ setSelectedCommentIndex(Math.max(0, selectedCommentIndex - 1));
518
+ }
519
+ });
520
+ return;
521
+ }
522
+ // Link to project (P key)
523
+ if (input === 'P' && tasks.projects.length > 0) {
524
+ setTaskToLink(selectedTask);
525
+ setProjectSelectIndex(0);
526
+ setMode('select-project');
527
+ return;
528
+ }
529
+ return;
530
+ }
403
531
  // Handle search mode
404
532
  if (mode === 'search') {
405
533
  if (key.escape) {
@@ -493,7 +621,13 @@ export function GtdDQ({ onOpenSettings }) {
493
621
  setInputValue('');
494
622
  return;
495
623
  }
496
- // TODO: implement setTaskContext
624
+ const task = currentTasks[selectedTaskIndex];
625
+ if (selected === 'clear') {
626
+ setTaskContext(task, null);
627
+ }
628
+ else {
629
+ setTaskContext(task, selected);
630
+ }
497
631
  setContextSelectIndex(0);
498
632
  setMode('normal');
499
633
  return;
@@ -503,9 +637,10 @@ export function GtdDQ({ onOpenSettings }) {
503
637
  // Handle select-project mode
504
638
  if (mode === 'select-project') {
505
639
  if (key.escape) {
640
+ const wasFromTaskDetail = selectedTask !== null;
506
641
  setTaskToLink(null);
507
642
  setProjectSelectIndex(0);
508
- setMode('normal');
643
+ setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
509
644
  return;
510
645
  }
511
646
  if (key.upArrow || input === 'k') {
@@ -518,10 +653,11 @@ export function GtdDQ({ onOpenSettings }) {
518
653
  }
519
654
  if (key.return && taskToLink && tasks.projects.length > 0) {
520
655
  const project = tasks.projects[projectSelectIndex];
656
+ const wasFromTaskDetail = selectedTask !== null;
521
657
  linkTaskToProject(taskToLink, project);
522
658
  setTaskToLink(null);
523
659
  setProjectSelectIndex(0);
524
- setMode('normal');
660
+ setMode(wasFromTaskDetail ? 'task-detail' : 'normal');
525
661
  return;
526
662
  }
527
663
  return;
@@ -641,6 +777,14 @@ export function GtdDQ({ onOpenSettings }) {
641
777
  setSelectedTaskIndex((prev) => (prev < currentTasks.length - 1 ? prev + 1 : 0));
642
778
  return;
643
779
  }
780
+ // Enter to view task details (on non-projects tabs)
781
+ if (key.return && currentTab !== 'projects' && currentTasks.length > 0) {
782
+ const task = currentTasks[selectedTaskIndex];
783
+ setSelectedTask(task);
784
+ loadTaskComments(task.id);
785
+ setMode('task-detail');
786
+ return;
787
+ }
644
788
  // Task actions
645
789
  if (currentTasks.length > 0) {
646
790
  const task = currentTasks[selectedTaskIndex];
@@ -708,6 +852,12 @@ export function GtdDQ({ onOpenSettings }) {
708
852
  setMode('confirm-delete');
709
853
  return;
710
854
  }
855
+ // Set context
856
+ if (input === 'c' && currentTab !== 'projects') {
857
+ setContextSelectIndex(0);
858
+ setMode('set-context');
859
+ return;
860
+ }
711
861
  }
712
862
  // Undo
713
863
  if (input === 'u') {
@@ -741,10 +891,13 @@ export function GtdDQ({ onOpenSettings }) {
741
891
  if (mode === 'help') {
742
892
  return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
743
893
  }
744
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: "FLOQ" }), _jsxs(Text, { color: theme.colors.textMuted, children: [" 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] }))] }), _jsx(Text, { color: theme.colors.textMuted, children: "?=help q=quit" })] }), 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) => {
894
+ 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] }))] }), _jsx(Text, { color: theme.colors.textMuted, children: "?=help q=quit" })] }), 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) => {
745
895
  const label = ctx === 'all' ? 'All' : ctx === 'none' ? 'No context' : `@${ctx}`;
746
896
  return (_jsxs(Text, { color: index === contextSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === contextSelectIndex, children: [index === contextSelectIndex ? '▶ ' : ' ', label] }, ctx));
747
- }) })] })) : 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 }) }) })) : (_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) => {
897
+ }) })] })) : 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) => {
898
+ const isSelected = index === selectedCommentIndex && mode === 'task-detail';
899
+ 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));
900
+ })) }) })] })) : (_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) => {
748
901
  const isSelected = index === currentTabIndex;
749
902
  const count = tasks[tab].length;
750
903
  return (_jsxs(Text, { color: isSelected && paneFocus === 'tabs' ? theme.colors.textSelected : theme.colors.text, bold: isSelected, children: [isSelected ? '▶ ' : ' ', index + 1, ":", getTabLabel(tab), " (", count, ")"] }, tab));
@@ -765,9 +918,11 @@ export function GtdDQ({ onOpenSettings }) {
765
918
  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));
766
919
  })) }) })] })), (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
767
920
  ? `${i18n.tui.newTask}[${selectedProject.title}] `
768
- : 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 === '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 }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: mode === 'project-detail'
769
- ? 'j/k=select a=add d=done Esc/b=back /=search'
770
- : paneFocus === 'tabs'
771
- ? 'j/k=select l/Enter=tasks 1-6=tab a=add @=filter /=search'
772
- : 'j/k=select h/Esc=back d=done n=next s=someday w=wait i=inbox p=project P=link D=delete u=undo /=search' }) })] }));
921
+ : 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 }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: mode === 'task-detail'
922
+ ? i18n.tui.dqFooter.taskDetail
923
+ : mode === 'project-detail'
924
+ ? i18n.tui.dqFooter.projectDetail
925
+ : paneFocus === 'tabs'
926
+ ? i18n.tui.dqFooter.tabs
927
+ : i18n.tui.dqFooter.tasks }) })] }));
773
928
  }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ type SettingsMode = 'none' | 'theme-select' | 'mode-select' | 'lang-select';
3
+ interface GtdMarioProps {
4
+ onOpenSettings?: (mode: SettingsMode) => void;
5
+ }
6
+ export declare function GtdMario({ onOpenSettings }: GtdMarioProps): React.ReactElement;
7
+ export {};