floq 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.ja.md +133 -0
  2. package/README.md +133 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.js +124 -0
  5. package/dist/commands/add.d.ts +6 -0
  6. package/dist/commands/add.js +36 -0
  7. package/dist/commands/config.d.ts +6 -0
  8. package/dist/commands/config.js +73 -0
  9. package/dist/commands/done.d.ts +1 -0
  10. package/dist/commands/done.js +37 -0
  11. package/dist/commands/list.d.ts +2 -0
  12. package/dist/commands/list.js +96 -0
  13. package/dist/commands/move.d.ts +1 -0
  14. package/dist/commands/move.js +50 -0
  15. package/dist/commands/project.d.ts +8 -0
  16. package/dist/commands/project.js +160 -0
  17. package/dist/config.d.ts +14 -0
  18. package/dist/config.js +64 -0
  19. package/dist/db/index.d.ts +6 -0
  20. package/dist/db/index.js +72 -0
  21. package/dist/db/schema.d.ts +192 -0
  22. package/dist/db/schema.js +13 -0
  23. package/dist/i18n/en.d.ts +160 -0
  24. package/dist/i18n/en.js +108 -0
  25. package/dist/i18n/index.d.ts +7 -0
  26. package/dist/i18n/index.js +15 -0
  27. package/dist/i18n/ja.d.ts +2 -0
  28. package/dist/i18n/ja.js +108 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +3 -0
  31. package/dist/paths.d.ts +6 -0
  32. package/dist/paths.js +22 -0
  33. package/dist/ui/App.d.ts +2 -0
  34. package/dist/ui/App.js +472 -0
  35. package/dist/ui/SplashScreen.d.ts +7 -0
  36. package/dist/ui/SplashScreen.js +61 -0
  37. package/dist/ui/TaskList.d.ts +10 -0
  38. package/dist/ui/TaskList.js +9 -0
  39. package/dist/ui/ThemeSelector.d.ts +7 -0
  40. package/dist/ui/ThemeSelector.js +17 -0
  41. package/dist/ui/components/FullScreenBox.d.ts +8 -0
  42. package/dist/ui/components/FullScreenBox.js +10 -0
  43. package/dist/ui/components/FunctionKeyBar.d.ts +10 -0
  44. package/dist/ui/components/FunctionKeyBar.js +19 -0
  45. package/dist/ui/components/HelpModal.d.ts +6 -0
  46. package/dist/ui/components/HelpModal.js +11 -0
  47. package/dist/ui/components/StatusBadge.d.ts +7 -0
  48. package/dist/ui/components/StatusBadge.js +16 -0
  49. package/dist/ui/components/TaskItem.d.ts +9 -0
  50. package/dist/ui/components/TaskItem.js +10 -0
  51. package/dist/ui/theme/index.d.ts +10 -0
  52. package/dist/ui/theme/index.js +11 -0
  53. package/dist/ui/theme/themes.d.ts +9 -0
  54. package/dist/ui/theme/themes.js +163 -0
  55. package/dist/ui/theme/types.d.ts +43 -0
  56. package/dist/ui/theme/types.js +1 -0
  57. package/dist/version.d.ts +1 -0
  58. package/dist/version.js +7 -0
  59. package/package.json +57 -0
package/dist/ui/App.js ADDED
@@ -0,0 +1,472 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text, useInput, useApp } from 'ink';
4
+ import TextInput from 'ink-text-input';
5
+ import { eq, and } from 'drizzle-orm';
6
+ import { v4 as uuidv4 } from 'uuid';
7
+ import { TaskItem } from './components/TaskItem.js';
8
+ import { HelpModal } from './components/HelpModal.js';
9
+ import { FunctionKeyBar } from './components/FunctionKeyBar.js';
10
+ import { SplashScreen } from './SplashScreen.js';
11
+ import { getDb, schema } from '../db/index.js';
12
+ import { t, fmt } from '../i18n/index.js';
13
+ import { ThemeProvider, useTheme } from './theme/index.js';
14
+ import { getThemeName } from '../config.js';
15
+ const TABS = ['inbox', 'next', 'waiting', 'someday', 'projects'];
16
+ export function App() {
17
+ const themeName = getThemeName();
18
+ return (_jsx(ThemeProvider, { themeName: themeName, children: _jsx(AppContent, {}) }));
19
+ }
20
+ function AppContent() {
21
+ const theme = useTheme();
22
+ const { exit } = useApp();
23
+ const [mode, setMode] = useState('splash');
24
+ const [inputValue, setInputValue] = useState('');
25
+ const [currentListIndex, setCurrentListIndex] = useState(0);
26
+ const [selectedTaskIndex, setSelectedTaskIndex] = useState(0);
27
+ const [tasks, setTasks] = useState({
28
+ inbox: [],
29
+ next: [],
30
+ waiting: [],
31
+ someday: [],
32
+ projects: [],
33
+ });
34
+ const [message, setMessage] = useState(null);
35
+ const [selectedProject, setSelectedProject] = useState(null);
36
+ const [projectTasks, setProjectTasks] = useState([]);
37
+ const [taskToLink, setTaskToLink] = useState(null);
38
+ const [projectSelectIndex, setProjectSelectIndex] = useState(0);
39
+ const i18n = t();
40
+ const loadTasks = () => {
41
+ const db = getDb();
42
+ const newTasks = {
43
+ inbox: [],
44
+ next: [],
45
+ waiting: [],
46
+ someday: [],
47
+ projects: [],
48
+ };
49
+ // Load all tasks (including project children) by status
50
+ const statusList = ['inbox', 'next', 'waiting', 'someday'];
51
+ for (const status of statusList) {
52
+ newTasks[status] = db
53
+ .select()
54
+ .from(schema.tasks)
55
+ .where(and(eq(schema.tasks.status, status), eq(schema.tasks.isProject, false)))
56
+ .all();
57
+ }
58
+ // Load projects (isProject = true, not done)
59
+ newTasks.projects = db
60
+ .select()
61
+ .from(schema.tasks)
62
+ .where(and(eq(schema.tasks.isProject, true), eq(schema.tasks.status, 'next')))
63
+ .all();
64
+ setTasks(newTasks);
65
+ };
66
+ // Get parent project for a task
67
+ const getParentProject = (parentId) => {
68
+ if (!parentId)
69
+ return undefined;
70
+ return tasks.projects.find(p => p.id === parentId);
71
+ };
72
+ const loadProjectTasks = (projectId) => {
73
+ const db = getDb();
74
+ const children = db
75
+ .select()
76
+ .from(schema.tasks)
77
+ .where(eq(schema.tasks.parentId, projectId))
78
+ .all();
79
+ setProjectTasks(children);
80
+ };
81
+ useEffect(() => {
82
+ loadTasks();
83
+ }, []);
84
+ const currentTab = TABS[currentListIndex];
85
+ const currentTasks = mode === 'project-detail' ? projectTasks : tasks[currentTab];
86
+ const addTask = (title, parentId) => {
87
+ if (!title.trim())
88
+ return;
89
+ const db = getDb();
90
+ const now = new Date();
91
+ db.insert(schema.tasks)
92
+ .values({
93
+ id: uuidv4(),
94
+ title: title.trim(),
95
+ status: parentId ? 'next' : 'inbox',
96
+ parentId: parentId || null,
97
+ createdAt: now,
98
+ updatedAt: now,
99
+ })
100
+ .run();
101
+ setMessage(fmt(i18n.tui.added, { title: title.trim() }));
102
+ loadTasks();
103
+ };
104
+ const handleInputSubmit = (value) => {
105
+ if (value.trim()) {
106
+ if (mode === 'add-to-project' && selectedProject) {
107
+ addTask(value, selectedProject.id);
108
+ loadProjectTasks(selectedProject.id);
109
+ setMode('project-detail');
110
+ }
111
+ else {
112
+ addTask(value);
113
+ setMode('normal');
114
+ }
115
+ }
116
+ else {
117
+ setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
118
+ }
119
+ setInputValue('');
120
+ };
121
+ const linkTaskToProject = (task, project) => {
122
+ const db = getDb();
123
+ db.update(schema.tasks)
124
+ .set({ parentId: project.id, updatedAt: new Date() })
125
+ .where(eq(schema.tasks.id, task.id))
126
+ .run();
127
+ setMessage(fmt(i18n.tui.linkedToProject || 'Linked "{title}" to {project}', { title: task.title, project: project.title }));
128
+ loadTasks();
129
+ };
130
+ const getTabLabel = (tab) => {
131
+ switch (tab) {
132
+ case 'inbox':
133
+ return i18n.tui.tabInbox;
134
+ case 'next':
135
+ return i18n.tui.tabNext;
136
+ case 'waiting':
137
+ return i18n.tui.tabWaiting;
138
+ case 'someday':
139
+ return i18n.tui.tabSomeday;
140
+ case 'projects':
141
+ return i18n.tui.tabProjects || 'Projects';
142
+ }
143
+ };
144
+ useInput((input, key) => {
145
+ // Skip splash screen on any key
146
+ if (mode === 'splash') {
147
+ setMode('normal');
148
+ return;
149
+ }
150
+ // Handle help mode - any key closes
151
+ if (mode === 'help') {
152
+ setMode('normal');
153
+ return;
154
+ }
155
+ // Handle add mode
156
+ if (mode === 'add' || mode === 'add-to-project') {
157
+ if (key.escape) {
158
+ setInputValue('');
159
+ setMode(mode === 'add-to-project' ? 'project-detail' : 'normal');
160
+ }
161
+ return;
162
+ }
163
+ // Handle select-project mode
164
+ if (mode === 'select-project') {
165
+ if (key.escape) {
166
+ setTaskToLink(null);
167
+ setProjectSelectIndex(0);
168
+ setMode('normal');
169
+ return;
170
+ }
171
+ // Navigate projects
172
+ if (key.upArrow || input === 'k') {
173
+ setProjectSelectIndex((prev) => (prev > 0 ? prev - 1 : tasks.projects.length - 1));
174
+ return;
175
+ }
176
+ if (key.downArrow || input === 'j') {
177
+ setProjectSelectIndex((prev) => (prev < tasks.projects.length - 1 ? prev + 1 : 0));
178
+ return;
179
+ }
180
+ // Select project with Enter
181
+ if (key.return && taskToLink && tasks.projects.length > 0) {
182
+ const project = tasks.projects[projectSelectIndex];
183
+ linkTaskToProject(taskToLink, project);
184
+ setTaskToLink(null);
185
+ setProjectSelectIndex(0);
186
+ setMode('normal');
187
+ return;
188
+ }
189
+ return;
190
+ }
191
+ // Handle project-detail mode
192
+ if (mode === 'project-detail') {
193
+ if (key.escape || key.backspace || input === 'b') {
194
+ setMode('normal');
195
+ setSelectedProject(null);
196
+ setProjectTasks([]);
197
+ setSelectedTaskIndex(0);
198
+ return;
199
+ }
200
+ // Add task to this project
201
+ if (input === 'a') {
202
+ setMode('add-to-project');
203
+ return;
204
+ }
205
+ // Navigate within project tasks
206
+ if (key.upArrow || input === 'k') {
207
+ setSelectedTaskIndex((prev) => (prev > 0 ? prev - 1 : projectTasks.length - 1));
208
+ return;
209
+ }
210
+ if (key.downArrow || input === 'j') {
211
+ setSelectedTaskIndex((prev) => (prev < projectTasks.length - 1 ? prev + 1 : 0));
212
+ return;
213
+ }
214
+ // Mark child task as done
215
+ if (input === 'd' && projectTasks.length > 0) {
216
+ const task = projectTasks[selectedTaskIndex];
217
+ const db = getDb();
218
+ db.update(schema.tasks)
219
+ .set({ status: 'done', updatedAt: new Date() })
220
+ .where(eq(schema.tasks.id, task.id))
221
+ .run();
222
+ setMessage(fmt(i18n.tui.completed, { title: task.title }));
223
+ if (selectedProject) {
224
+ loadProjectTasks(selectedProject.id);
225
+ }
226
+ loadTasks();
227
+ return;
228
+ }
229
+ return;
230
+ }
231
+ // Clear message on any input
232
+ if (message) {
233
+ setMessage(null);
234
+ }
235
+ // Function key support (escape sequences)
236
+ // F1 = Help, F2 = Add, F3 = Done, F4 = Next, F5 = Refresh, F10 = Quit
237
+ if (input === '\x1bOP' || input === '\x1b[11~') {
238
+ // F1 - Help
239
+ setMode('help');
240
+ return;
241
+ }
242
+ if (input === '\x1bOQ' || input === '\x1b[12~') {
243
+ // F2 - Add
244
+ setMode('add');
245
+ return;
246
+ }
247
+ if ((input === '\x1bOR' || input === '\x1b[13~') && currentTasks.length > 0) {
248
+ // F3 - Done
249
+ const task = currentTasks[selectedTaskIndex];
250
+ const db = getDb();
251
+ db.update(schema.tasks)
252
+ .set({ status: 'done', updatedAt: new Date() })
253
+ .where(eq(schema.tasks.id, task.id))
254
+ .run();
255
+ setMessage(fmt(i18n.tui.completed, { title: task.title }));
256
+ loadTasks();
257
+ if (selectedTaskIndex >= currentTasks.length - 1) {
258
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
259
+ }
260
+ return;
261
+ }
262
+ if ((input === '\x1bOS' || input === '\x1b[14~') && currentTasks.length > 0 && currentTab !== 'next' && currentTab !== 'projects') {
263
+ // F4 - Move to Next
264
+ const task = currentTasks[selectedTaskIndex];
265
+ const db = getDb();
266
+ db.update(schema.tasks)
267
+ .set({ status: 'next', waitingFor: null, updatedAt: new Date() })
268
+ .where(eq(schema.tasks.id, task.id))
269
+ .run();
270
+ setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status.next }));
271
+ loadTasks();
272
+ if (selectedTaskIndex >= currentTasks.length - 1) {
273
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
274
+ }
275
+ return;
276
+ }
277
+ if (input === '\x1b[15~') {
278
+ // F5 - Refresh
279
+ loadTasks();
280
+ setMessage(i18n.tui.refreshed);
281
+ return;
282
+ }
283
+ if (input === '\x1b[21~') {
284
+ // F10 - Quit
285
+ exit();
286
+ return;
287
+ }
288
+ // Show help
289
+ if (input === '?') {
290
+ setMode('help');
291
+ return;
292
+ }
293
+ // Quit
294
+ if (input === 'q' || (key.ctrl && input === 'c')) {
295
+ exit();
296
+ return;
297
+ }
298
+ // Add task
299
+ if (input === 'a') {
300
+ setMode('add');
301
+ return;
302
+ }
303
+ // Direct tab switch with number keys
304
+ if (input === '1') {
305
+ setCurrentListIndex(0);
306
+ setSelectedTaskIndex(0);
307
+ return;
308
+ }
309
+ if (input === '2') {
310
+ setCurrentListIndex(1);
311
+ setSelectedTaskIndex(0);
312
+ return;
313
+ }
314
+ if (input === '3') {
315
+ setCurrentListIndex(2);
316
+ setSelectedTaskIndex(0);
317
+ return;
318
+ }
319
+ if (input === '4') {
320
+ setCurrentListIndex(3);
321
+ setSelectedTaskIndex(0);
322
+ return;
323
+ }
324
+ if (input === '5') {
325
+ setCurrentListIndex(4);
326
+ setSelectedTaskIndex(0);
327
+ return;
328
+ }
329
+ // Navigate between lists
330
+ if (key.leftArrow || input === 'h') {
331
+ setCurrentListIndex((prev) => (prev > 0 ? prev - 1 : TABS.length - 1));
332
+ setSelectedTaskIndex(0);
333
+ return;
334
+ }
335
+ if (key.rightArrow || input === 'l') {
336
+ setCurrentListIndex((prev) => (prev < TABS.length - 1 ? prev + 1 : 0));
337
+ setSelectedTaskIndex(0);
338
+ return;
339
+ }
340
+ // Navigate within list
341
+ if (key.upArrow || input === 'k') {
342
+ setSelectedTaskIndex((prev) => (prev > 0 ? prev - 1 : currentTasks.length - 1));
343
+ return;
344
+ }
345
+ if (key.downArrow || input === 'j') {
346
+ setSelectedTaskIndex((prev) => (prev < currentTasks.length - 1 ? prev + 1 : 0));
347
+ return;
348
+ }
349
+ // Enter to view project details (on projects tab)
350
+ if (key.return && currentTab === 'projects' && currentTasks.length > 0) {
351
+ const project = currentTasks[selectedTaskIndex];
352
+ setSelectedProject(project);
353
+ loadProjectTasks(project.id);
354
+ setMode('project-detail');
355
+ setSelectedTaskIndex(0);
356
+ return;
357
+ }
358
+ // Mark as project (p key)
359
+ if (input === 'p' && currentTasks.length > 0 && currentTab !== 'projects') {
360
+ const task = currentTasks[selectedTaskIndex];
361
+ const db = getDb();
362
+ db.update(schema.tasks)
363
+ .set({ isProject: true, status: 'next', updatedAt: new Date() })
364
+ .where(eq(schema.tasks.id, task.id))
365
+ .run();
366
+ setMessage(fmt(i18n.tui.madeProject || 'Made project: {title}', { title: task.title }));
367
+ loadTasks();
368
+ if (selectedTaskIndex >= currentTasks.length - 1) {
369
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
370
+ }
371
+ return;
372
+ }
373
+ // Link to project (P key - shift+p)
374
+ if (input === 'P' && currentTasks.length > 0 && currentTab !== 'projects' && tasks.projects.length > 0) {
375
+ const task = currentTasks[selectedTaskIndex];
376
+ setTaskToLink(task);
377
+ setProjectSelectIndex(0);
378
+ setMode('select-project');
379
+ return;
380
+ }
381
+ // Mark as done
382
+ if (input === 'd' && currentTasks.length > 0) {
383
+ const task = currentTasks[selectedTaskIndex];
384
+ const db = getDb();
385
+ db.update(schema.tasks)
386
+ .set({ status: 'done', updatedAt: new Date() })
387
+ .where(eq(schema.tasks.id, task.id))
388
+ .run();
389
+ setMessage(fmt(i18n.tui.completed, { title: task.title }));
390
+ loadTasks();
391
+ if (selectedTaskIndex >= currentTasks.length - 1) {
392
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
393
+ }
394
+ return;
395
+ }
396
+ // Move to next actions
397
+ if (input === 'n' && currentTasks.length > 0 && currentTab !== 'next' && currentTab !== 'projects') {
398
+ const task = currentTasks[selectedTaskIndex];
399
+ const db = getDb();
400
+ db.update(schema.tasks)
401
+ .set({ status: 'next', waitingFor: null, updatedAt: new Date() })
402
+ .where(eq(schema.tasks.id, task.id))
403
+ .run();
404
+ setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status.next }));
405
+ loadTasks();
406
+ if (selectedTaskIndex >= currentTasks.length - 1) {
407
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
408
+ }
409
+ return;
410
+ }
411
+ // Move to someday
412
+ if (input === 's' && currentTasks.length > 0 && currentTab !== 'someday' && currentTab !== 'projects') {
413
+ const task = currentTasks[selectedTaskIndex];
414
+ const db = getDb();
415
+ db.update(schema.tasks)
416
+ .set({ status: 'someday', waitingFor: null, updatedAt: new Date() })
417
+ .where(eq(schema.tasks.id, task.id))
418
+ .run();
419
+ setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status.someday }));
420
+ loadTasks();
421
+ if (selectedTaskIndex >= currentTasks.length - 1) {
422
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
423
+ }
424
+ return;
425
+ }
426
+ // Move to inbox
427
+ if (input === 'i' && currentTasks.length > 0 && currentTab !== 'inbox' && currentTab !== 'projects') {
428
+ const task = currentTasks[selectedTaskIndex];
429
+ const db = getDb();
430
+ db.update(schema.tasks)
431
+ .set({ status: 'inbox', waitingFor: null, updatedAt: new Date() })
432
+ .where(eq(schema.tasks.id, task.id))
433
+ .run();
434
+ setMessage(fmt(i18n.tui.movedTo, { title: task.title, status: i18n.status.inbox }));
435
+ loadTasks();
436
+ if (selectedTaskIndex >= currentTasks.length - 1) {
437
+ setSelectedTaskIndex(Math.max(0, selectedTaskIndex - 1));
438
+ }
439
+ return;
440
+ }
441
+ // Refresh
442
+ if (input === 'r') {
443
+ loadTasks();
444
+ setMessage(i18n.tui.refreshed);
445
+ return;
446
+ }
447
+ });
448
+ // Splash screen
449
+ if (mode === 'splash') {
450
+ return _jsx(SplashScreen, { onComplete: () => setMode('normal') });
451
+ }
452
+ // Help modal overlay
453
+ if (mode === 'help') {
454
+ return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsx(HelpModal, { onClose: () => setMode('normal') }) }));
455
+ }
456
+ const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
457
+ const formatTabLabel = (label, isActive) => {
458
+ const [open, close] = theme.style.tabBrackets;
459
+ return `${open}${label}${close}`;
460
+ };
461
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: formatTitle(i18n.tui.title) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.helpHint })] }), _jsx(Box, { marginBottom: 1, children: TABS.map((tab, index) => {
462
+ const isActive = index === currentListIndex && mode !== 'project-detail';
463
+ const count = tasks[tab].length;
464
+ const label = `${index + 1}:${getTabLabel(tab)}(${count})`;
465
+ return (_jsx(Box, { marginRight: 1, children: _jsx(Text, { color: isActive ? theme.colors.textSelected : theme.colors.textMuted, bold: isActive, inverse: isActive && theme.style.tabActiveInverse, children: formatTabLabel(` ${label} `, isActive) }) }, tab));
466
+ }) }), mode === 'project-detail' && selectedProject && (_jsxs(Box, { marginBottom: 1, children: [_jsxs(Text, { color: theme.colors.accent, bold: true, children: [theme.name === 'modern' ? '📁 ' : '>> ', selectedProject.title] }), _jsxs(Text, { color: theme.colors.textMuted, children: [" (Esc/b: ", i18n.tui.back || 'back', ")"] })] })), _jsx(Box, { flexDirection: "column", borderStyle: theme.borders.list, borderColor: theme.colors.border, paddingX: 1, paddingY: 1, minHeight: 10, children: currentTasks.length === 0 ? (_jsx(Text, { color: theme.colors.textMuted, italic: true, children: i18n.tui.noTasks })) : (currentTasks.map((task, index) => {
467
+ const parentProject = getParentProject(task.parentId);
468
+ return (_jsx(TaskItem, { task: task, isSelected: index === selectedTaskIndex, projectName: parentProject?.title }, task.id));
469
+ })) }), (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
470
+ ? `${i18n.tui.newTask}[${selectedProject.title}] `
471
+ : i18n.tui.newTask }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit, placeholder: i18n.tui.placeholder }), _jsxs(Text, { color: theme.colors.textMuted, children: [" ", i18n.tui.inputHelp] })] })), mode === 'select-project' && taskToLink && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: theme.colors.secondary, bold: true, children: [i18n.tui.selectProject || 'Select project for', ": ", taskToLink.title] }), _jsx(Box, { flexDirection: "column", marginTop: 1, borderStyle: theme.borders.list, borderColor: theme.colors.borderActive, paddingX: 1, children: tasks.projects.map((project, index) => (_jsxs(Text, { color: index === projectSelectIndex ? theme.colors.textSelected : theme.colors.text, bold: index === projectSelectIndex, children: [index === projectSelectIndex ? theme.style.selectedPrefix : theme.style.unselectedPrefix, project.title] }, project.id))) }), _jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.selectProjectHelp || 'j/k: select, Enter: confirm, Esc: cancel' })] })), message && mode === 'normal' && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textHighlight, children: message }) })), _jsx(Box, { marginTop: 1, children: theme.style.showFunctionKeys ? (_jsx(FunctionKeyBar, {})) : (_jsx(Text, { color: theme.colors.textMuted, children: i18n.tui.footer })) })] }));
472
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ interface SplashScreenProps {
3
+ onComplete: () => void;
4
+ duration?: number;
5
+ }
6
+ export declare function SplashScreen({ onComplete, duration }: SplashScreenProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { useTheme } from './theme/index.js';
5
+ import { VERSION } from '../version.js';
6
+ const LOGO_MODERN = `
7
+ ███████╗██╗ ██████╗ ██████╗
8
+ ██╔════╝██║ ██╔═══██╗██╔═══██╗
9
+ █████╗ ██║ ██║ ██║██║ ██║
10
+ ██╔══╝ ██║ ██║ ██║██║▄▄ ██║
11
+ ██║ ███████╗╚██████╔╝╚██████╔╝
12
+ ╚═╝ ╚══════╝ ╚═════╝ ╚══▀▀═╝
13
+ `;
14
+ const LOGO_DOS = `
15
+ ╔═══════════════════════════════════╗
16
+ ║ ███████ ██ ██████ ██████ ║
17
+ ║ ██ ██ ██ ██ ██ ██║
18
+ ║ █████ ██ ██ ██ ██ ██║
19
+ ║ ██ ██ ██ ██ ██ ▄▄ ██║
20
+ ║ ██ ███████ ██████ ██████ ║
21
+ ╚═══════════════════════════════════╝
22
+ `;
23
+ const TAGLINE = 'Flow your tasks, clear your mind';
24
+ export function SplashScreen({ onComplete, duration = 1500 }) {
25
+ const [frame, setFrame] = useState(0);
26
+ const [showTagline, setShowTagline] = useState(false);
27
+ const theme = useTheme();
28
+ const isDosStyle = theme.name !== 'modern';
29
+ const logo = isDosStyle ? LOGO_DOS : LOGO_MODERN;
30
+ const [filled, empty] = theme.style.loadingChars;
31
+ useEffect(() => {
32
+ // Animate logo appearance
33
+ const frameInterval = setInterval(() => {
34
+ setFrame((prev) => {
35
+ if (prev >= 10) {
36
+ clearInterval(frameInterval);
37
+ return prev;
38
+ }
39
+ return prev + 1;
40
+ });
41
+ }, 50);
42
+ // Show tagline after logo appears
43
+ const taglineTimer = setTimeout(() => {
44
+ setShowTagline(true);
45
+ }, 600);
46
+ // Complete splash screen
47
+ const completeTimer = setTimeout(() => {
48
+ onComplete();
49
+ }, duration);
50
+ return () => {
51
+ clearInterval(frameInterval);
52
+ clearTimeout(taglineTimer);
53
+ clearTimeout(completeTimer);
54
+ };
55
+ }, [onComplete, duration]);
56
+ const logoLines = logo.split('\n');
57
+ const visibleLines = Math.min(Math.floor(frame * logoLines.length / 10), logoLines.length);
58
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", padding: 2, children: [_jsx(Box, { flexDirection: "column", alignItems: "center", children: logoLines.slice(0, visibleLines).map((line, index) => (_jsx(Text, { color: theme.colors.secondary, bold: true, children: line }, index))) }), showTagline && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, italic: !isDosStyle, children: isDosStyle ? `[ ${TAGLINE.toUpperCase()} ]` : TAGLINE }) })), _jsx(Box, { marginTop: 2, children: _jsx(Text, { color: theme.colors.primary, children: frame < 10
59
+ ? filled.repeat(frame) + empty.repeat(10 - frame)
60
+ : filled.repeat(10) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, dimColor: !isDosStyle, children: isDosStyle ? `VER ${VERSION}` : `v${VERSION}` }) })] }));
61
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { Task, TaskStatus } from '../db/schema.js';
3
+ interface TaskListProps {
4
+ status: TaskStatus;
5
+ tasks: Task[];
6
+ selectedIndex: number;
7
+ isActiveList: boolean;
8
+ }
9
+ export declare function TaskList({ status, tasks, selectedIndex, isActiveList, }: TaskListProps): React.ReactElement;
10
+ export {};
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { TaskItem } from './components/TaskItem.js';
4
+ import { StatusBadge } from './components/StatusBadge.js';
5
+ import { t } from '../i18n/index.js';
6
+ export function TaskList({ status, tasks, selectedIndex, isActiveList, }) {
7
+ const i18n = t();
8
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(StatusBadge, { status: status }), _jsxs(Text, { color: "gray", children: [" (", tasks.length, ")"] })] }), _jsx(Box, { flexDirection: "column", paddingLeft: 1, children: tasks.length === 0 ? (_jsx(Text, { color: "gray", italic: true, children: i18n.tui.noTasks })) : (tasks.map((task, index) => (_jsx(TaskItem, { task: task, isSelected: isActiveList && index === selectedIndex }, task.id)))) })] }));
9
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { type ThemeName } from '../config.js';
3
+ interface ThemeSelectorProps {
4
+ onSelect: (theme: ThemeName) => void;
5
+ }
6
+ export declare function ThemeSelector({ onSelect }: ThemeSelectorProps): React.ReactElement;
7
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { Select } from '@inkjs/ui';
4
+ import { themes, VALID_THEMES } from './theme/themes.js';
5
+ import { getThemeName } from '../config.js';
6
+ export function ThemeSelector({ onSelect }) {
7
+ const currentTheme = getThemeName();
8
+ const options = VALID_THEMES.map((themeName) => {
9
+ const theme = themes[themeName];
10
+ const isCurrent = themeName === currentTheme;
11
+ return {
12
+ label: `${theme.displayName}${isCurrent ? ' (current)' : ''}`,
13
+ value: themeName,
14
+ };
15
+ });
16
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, children: "Select a theme:" }), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: options, onChange: (value) => onSelect(value) }) })] }));
17
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface FullScreenBoxProps {
3
+ children: React.ReactNode;
4
+ backgroundColor?: string;
5
+ padding?: number;
6
+ }
7
+ export declare function FullScreenBox({ children, backgroundColor, padding }: FullScreenBoxProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, useStdout } from 'ink';
3
+ export function FullScreenBox({ children, backgroundColor, padding = 1 }) {
4
+ const { stdout } = useStdout();
5
+ const width = stdout?.columns || 80;
6
+ const height = stdout?.rows || 24;
7
+ // 背景色で画面全体を埋めるために、各行に空白を追加
8
+ const fillLine = backgroundColor ? ' '.repeat(width) : '';
9
+ return (_jsxs(Box, { flexDirection: "column", width: width, minHeight: height, padding: padding, ...(backgroundColor ? { backgroundColor } : {}), children: [children, backgroundColor && (_jsx(Box, { flexGrow: 1, width: width, children: _jsx(Box, { backgroundColor: backgroundColor, width: width }) }))] }));
10
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ interface FunctionKey {
3
+ key: string;
4
+ label: string;
5
+ }
6
+ interface FunctionKeyBarProps {
7
+ keys?: FunctionKey[];
8
+ }
9
+ export declare function FunctionKeyBar({ keys }: FunctionKeyBarProps): React.ReactElement;
10
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useTheme } from '../theme/index.js';
4
+ const DEFAULT_KEYS = [
5
+ { key: 'F1', label: 'Help' },
6
+ { key: 'F2', label: 'Add' },
7
+ { key: 'F3', label: 'Done' },
8
+ { key: 'F4', label: 'Next' },
9
+ { key: 'F5', label: 'Rfrsh' },
10
+ { key: 'F6', label: '' },
11
+ { key: 'F7', label: '' },
12
+ { key: 'F8', label: '' },
13
+ { key: 'F9', label: '' },
14
+ { key: 'F10', label: 'Quit' },
15
+ ];
16
+ export function FunctionKeyBar({ keys = DEFAULT_KEYS }) {
17
+ const theme = useTheme();
18
+ return (_jsx(Box, { children: keys.map((fn, index) => (_jsxs(Box, { marginRight: 0, children: [_jsx(Text, { backgroundColor: theme.colors.fnKeyLabel, color: "white", bold: true, children: fn.key }), _jsx(Text, { backgroundColor: theme.colors.fnKeyText, color: "black", children: fn.label.padEnd(5, ' ') })] }, index))) }));
19
+ }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface HelpModalProps {
3
+ onClose: () => void;
4
+ }
5
+ export declare function HelpModal({ onClose }: HelpModalProps): React.ReactElement;
6
+ export {};