floq 0.1.0 → 0.2.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 (42) hide show
  1. package/README.ja.md +89 -9
  2. package/README.md +89 -9
  3. package/dist/changelog.d.ts +13 -0
  4. package/dist/changelog.js +95 -0
  5. package/dist/cli.js +44 -1
  6. package/dist/commands/comment.d.ts +2 -0
  7. package/dist/commands/comment.js +67 -0
  8. package/dist/commands/config.d.ts +4 -0
  9. package/dist/commands/config.js +123 -1
  10. package/dist/commands/setup.d.ts +1 -0
  11. package/dist/commands/setup.js +13 -0
  12. package/dist/config.d.ts +5 -0
  13. package/dist/config.js +40 -3
  14. package/dist/db/index.js +20 -0
  15. package/dist/db/schema.d.ts +83 -0
  16. package/dist/db/schema.js +6 -0
  17. package/dist/i18n/en.d.ts +278 -0
  18. package/dist/i18n/en.js +148 -3
  19. package/dist/i18n/ja.js +148 -3
  20. package/dist/index.js +14 -4
  21. package/dist/paths.d.ts +4 -0
  22. package/dist/paths.js +63 -5
  23. package/dist/ui/App.js +320 -37
  24. package/dist/ui/ModeSelector.d.ts +7 -0
  25. package/dist/ui/ModeSelector.js +37 -0
  26. package/dist/ui/SetupWizard.d.ts +6 -0
  27. package/dist/ui/SetupWizard.js +321 -0
  28. package/dist/ui/components/HelpModal.d.ts +2 -1
  29. package/dist/ui/components/HelpModal.js +155 -4
  30. package/dist/ui/components/KanbanBoard.d.ts +6 -0
  31. package/dist/ui/components/KanbanBoard.js +498 -0
  32. package/dist/ui/components/KanbanColumn.d.ts +12 -0
  33. package/dist/ui/components/KanbanColumn.js +11 -0
  34. package/dist/ui/components/ProgressBar.d.ts +7 -0
  35. package/dist/ui/components/ProgressBar.js +13 -0
  36. package/dist/ui/components/SearchBar.d.ts +8 -0
  37. package/dist/ui/components/SearchBar.js +11 -0
  38. package/dist/ui/components/SearchResults.d.ts +9 -0
  39. package/dist/ui/components/SearchResults.js +18 -0
  40. package/dist/ui/components/TaskItem.d.ts +6 -1
  41. package/dist/ui/components/TaskItem.js +3 -2
  42. package/package.json +1 -1
@@ -0,0 +1,321 @@
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 { themes, VALID_THEMES } from './theme/themes.js';
6
+ import { saveConfig } from '../config.js';
7
+ import { t } from '../i18n/index.js';
8
+ const LOCALES = [
9
+ { value: 'en', label: 'English', desc: 'English language' },
10
+ { value: 'ja', label: '日本語', desc: 'Japanese language' },
11
+ ];
12
+ const VIEW_MODES = [
13
+ { value: 'gtd', labelKey: 'gtd', descKey: 'gtdDesc' },
14
+ { value: 'kanban', labelKey: 'kanban', descKey: 'kanbanDesc' },
15
+ ];
16
+ const DB_OPTIONS = [
17
+ { value: 'local', labelKey: 'local', descKey: 'localDesc' },
18
+ { value: 'turso', labelKey: 'turso', descKey: 'tursoDesc' },
19
+ ];
20
+ export function SetupWizard({ onComplete }) {
21
+ const { exit } = useApp();
22
+ const [step, setStep] = useState('welcome');
23
+ const [state, setState] = useState({
24
+ locale: 'en',
25
+ theme: 'modern',
26
+ viewMode: 'gtd',
27
+ useTurso: false,
28
+ tursoUrl: '',
29
+ tursoToken: '',
30
+ });
31
+ // Selection indexes for various steps
32
+ const [languageIndex, setLanguageIndex] = useState(0);
33
+ const [themeIndex, setThemeIndex] = useState(0);
34
+ const [viewModeIndex, setViewModeIndex] = useState(0);
35
+ const [dbIndex, setDbIndex] = useState(0);
36
+ // Input values for Turso
37
+ const [inputValue, setInputValue] = useState('');
38
+ const [inputError, setInputError] = useState(null);
39
+ // Get translations - use selected locale for setup wizard
40
+ const [i18n, setI18n] = useState(t());
41
+ // Update translations when locale changes
42
+ useEffect(() => {
43
+ // Re-get translations when state.locale changes
44
+ // We need to temporarily set the config locale to get correct translations
45
+ const translations = t();
46
+ setI18n(translations);
47
+ }, []);
48
+ // Get setup translations based on selected locale
49
+ const getSetupI18n = () => {
50
+ // Use dynamic import to get correct language translations
51
+ if (state.locale === 'ja') {
52
+ return {
53
+ welcome: {
54
+ title: 'Floqへようこそ!',
55
+ subtitle: 'タスクマネージャーを設定しましょう',
56
+ instruction: '任意のキーを押して設定を開始...',
57
+ },
58
+ language: {
59
+ title: '言語を選択',
60
+ hint: 'j/k: 選択, Enter: 確定, Esc: 戻る',
61
+ },
62
+ theme: {
63
+ title: 'テーマを選択',
64
+ hint: 'j/k: 選択, Enter: 確定, Esc: 戻る',
65
+ },
66
+ viewMode: {
67
+ title: '表示モードを選択',
68
+ hint: 'j/k: 選択, Enter: 確定, Esc: 戻る',
69
+ gtd: 'GTD (Getting Things Done)',
70
+ gtdDesc: 'Inbox、次のアクション、連絡待ち、いつかやるリストによるクラシックGTDワークフロー',
71
+ kanban: 'Kanbanボード',
72
+ kanbanDesc: '3カラムのKanbanボード表示',
73
+ },
74
+ database: {
75
+ title: 'データベースモードを選択',
76
+ local: 'ローカル',
77
+ localDesc: 'このデバイスにデータをローカル保存',
78
+ turso: 'Turso Cloud',
79
+ tursoDesc: 'Turso経由でデバイス間でデータを同期',
80
+ hint: 'j/k: 選択, Enter: 確定, Esc: 戻る',
81
+ },
82
+ turso: {
83
+ urlPrompt: 'TursoデータベースURL',
84
+ urlPlaceholder: 'libsql://your-db.turso.io',
85
+ urlError: 'URLはlibsql://で始まる必要があります',
86
+ tokenPrompt: 'Turso認証トークン',
87
+ tokenError: 'トークンは空にできません',
88
+ inputHint: 'Enter: 確定, Esc: 戻る',
89
+ },
90
+ complete: {
91
+ title: '設定完了!',
92
+ summary: '設定内容:',
93
+ language: '言語',
94
+ theme: 'テーマ',
95
+ viewMode: '表示モード',
96
+ database: 'データベース',
97
+ confirm: 'Enterを押してFloqを開始...',
98
+ },
99
+ };
100
+ }
101
+ return i18n.setup;
102
+ };
103
+ const setupI18n = getSetupI18n();
104
+ const handleSave = () => {
105
+ const tursoConfig = state.useTurso
106
+ ? { url: state.tursoUrl, authToken: state.tursoToken }
107
+ : undefined;
108
+ saveConfig({
109
+ locale: state.locale,
110
+ theme: state.theme,
111
+ viewMode: state.viewMode,
112
+ turso: tursoConfig,
113
+ });
114
+ onComplete();
115
+ };
116
+ const goToPrevStep = () => {
117
+ switch (step) {
118
+ case 'welcome':
119
+ exit();
120
+ break;
121
+ case 'language':
122
+ setStep('welcome');
123
+ break;
124
+ case 'theme':
125
+ setStep('language');
126
+ break;
127
+ case 'viewMode':
128
+ setStep('theme');
129
+ break;
130
+ case 'database':
131
+ setStep('viewMode');
132
+ break;
133
+ case 'turso-url':
134
+ setStep('database');
135
+ setInputValue('');
136
+ setInputError(null);
137
+ break;
138
+ case 'turso-token':
139
+ setStep('turso-url');
140
+ setInputValue(state.tursoUrl);
141
+ setInputError(null);
142
+ break;
143
+ case 'complete':
144
+ setStep('database');
145
+ break;
146
+ }
147
+ };
148
+ useInput((input, key) => {
149
+ // Welcome step - any key continues
150
+ if (step === 'welcome') {
151
+ if (key.escape) {
152
+ exit();
153
+ }
154
+ else {
155
+ setStep('language');
156
+ }
157
+ return;
158
+ }
159
+ // Input steps (turso-url and turso-token)
160
+ if (step === 'turso-url' || step === 'turso-token') {
161
+ if (key.escape) {
162
+ goToPrevStep();
163
+ }
164
+ return;
165
+ }
166
+ // Complete step - Enter saves
167
+ if (step === 'complete') {
168
+ if (key.return) {
169
+ handleSave();
170
+ }
171
+ else if (key.escape) {
172
+ goToPrevStep();
173
+ }
174
+ return;
175
+ }
176
+ // Selection steps
177
+ if (key.escape) {
178
+ goToPrevStep();
179
+ return;
180
+ }
181
+ // j/k or arrow navigation
182
+ if (input === 'j' || key.downArrow) {
183
+ switch (step) {
184
+ case 'language':
185
+ setLanguageIndex((prev) => (prev < LOCALES.length - 1 ? prev + 1 : 0));
186
+ break;
187
+ case 'theme':
188
+ setThemeIndex((prev) => (prev < VALID_THEMES.length - 1 ? prev + 1 : 0));
189
+ break;
190
+ case 'viewMode':
191
+ setViewModeIndex((prev) => (prev < VIEW_MODES.length - 1 ? prev + 1 : 0));
192
+ break;
193
+ case 'database':
194
+ setDbIndex((prev) => (prev < DB_OPTIONS.length - 1 ? prev + 1 : 0));
195
+ break;
196
+ }
197
+ return;
198
+ }
199
+ if (input === 'k' || key.upArrow) {
200
+ switch (step) {
201
+ case 'language':
202
+ setLanguageIndex((prev) => (prev > 0 ? prev - 1 : LOCALES.length - 1));
203
+ break;
204
+ case 'theme':
205
+ setThemeIndex((prev) => (prev > 0 ? prev - 1 : VALID_THEMES.length - 1));
206
+ break;
207
+ case 'viewMode':
208
+ setViewModeIndex((prev) => (prev > 0 ? prev - 1 : VIEW_MODES.length - 1));
209
+ break;
210
+ case 'database':
211
+ setDbIndex((prev) => (prev > 0 ? prev - 1 : DB_OPTIONS.length - 1));
212
+ break;
213
+ }
214
+ return;
215
+ }
216
+ // Enter to confirm selection
217
+ if (key.return) {
218
+ switch (step) {
219
+ case 'language':
220
+ setState((prev) => ({ ...prev, locale: LOCALES[languageIndex].value }));
221
+ setStep('theme');
222
+ break;
223
+ case 'theme':
224
+ setState((prev) => ({ ...prev, theme: VALID_THEMES[themeIndex] }));
225
+ setStep('viewMode');
226
+ break;
227
+ case 'viewMode':
228
+ setState((prev) => ({ ...prev, viewMode: VIEW_MODES[viewModeIndex].value }));
229
+ setStep('database');
230
+ break;
231
+ case 'database':
232
+ const useTurso = DB_OPTIONS[dbIndex].value === 'turso';
233
+ setState((prev) => ({ ...prev, useTurso }));
234
+ if (useTurso) {
235
+ setStep('turso-url');
236
+ }
237
+ else {
238
+ setStep('complete');
239
+ }
240
+ break;
241
+ }
242
+ }
243
+ });
244
+ const handleTursoUrlSubmit = (value) => {
245
+ if (!value.startsWith('libsql://')) {
246
+ setInputError(setupI18n.turso.urlError);
247
+ return;
248
+ }
249
+ setState((prev) => ({ ...prev, tursoUrl: value }));
250
+ setInputValue('');
251
+ setInputError(null);
252
+ setStep('turso-token');
253
+ };
254
+ const handleTursoTokenSubmit = (value) => {
255
+ if (!value.trim()) {
256
+ setInputError(setupI18n.turso.tokenError);
257
+ return;
258
+ }
259
+ setState((prev) => ({ ...prev, tursoToken: value.trim() }));
260
+ setInputValue('');
261
+ setInputError(null);
262
+ setStep('complete');
263
+ };
264
+ // Render Welcome step
265
+ if (step === 'welcome') {
266
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, alignItems: "center", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: `
267
+ ███████╗██╗ ██████╗ ██████╗
268
+ ██╔════╝██║ ██╔═══██╗██╔═══██╗
269
+ █████╗ ██║ ██║ ██║██║ ██║
270
+ ██╔══╝ ██║ ██║ ██║██║▄▄ ██║
271
+ ██║ ███████╗╚██████╔╝╚██████╔╝
272
+ ╚═╝ ╚══════╝ ╚═════╝ ╚══▀▀═╝
273
+ ` }) }), _jsx(Text, { bold: true, color: "green", children: setupI18n.welcome.title }), _jsx(Text, { color: "white", children: setupI18n.welcome.subtitle }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { dimColor: true, children: setupI18n.welcome.instruction }) })] }));
274
+ }
275
+ // Render Language selection
276
+ if (step === 'language') {
277
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: setupI18n.language.title }), _jsx(Text, { dimColor: true, children: setupI18n.language.hint }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: LOCALES.map((locale, index) => {
278
+ const isSelected = index === languageIndex;
279
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [isSelected ? '› ' : ' ', locale.label] }), _jsxs(Text, { dimColor: true, children: [" ", locale.desc] })] }, locale.value));
280
+ }) })] }));
281
+ }
282
+ // Render Theme selection
283
+ if (step === 'theme') {
284
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: setupI18n.theme.title }), _jsx(Text, { dimColor: true, children: setupI18n.theme.hint }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VALID_THEMES.map((themeName, index) => {
285
+ const theme = themes[themeName];
286
+ const isSelected = index === themeIndex;
287
+ return (_jsx(Box, { children: _jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [isSelected ? '› ' : ' ', theme.displayName] }) }, themeName));
288
+ }) })] }));
289
+ }
290
+ // Render View Mode selection
291
+ if (step === 'viewMode') {
292
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: setupI18n.viewMode.title }), _jsx(Text, { dimColor: true, children: setupI18n.viewMode.hint }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: VIEW_MODES.map((mode, index) => {
293
+ const isSelected = index === viewModeIndex;
294
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [isSelected ? '› ' : ' ', setupI18n.viewMode[mode.labelKey]] }), _jsxs(Text, { dimColor: true, children: [" ", setupI18n.viewMode[mode.descKey]] })] }, mode.value));
295
+ }) })] }));
296
+ }
297
+ // Render Database selection
298
+ if (step === 'database') {
299
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: setupI18n.database.title }), _jsx(Text, { dimColor: true, children: setupI18n.database.hint }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: DB_OPTIONS.map((option, index) => {
300
+ const isSelected = index === dbIndex;
301
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, children: [isSelected ? '› ' : ' ', setupI18n.database[option.labelKey]] }), _jsxs(Text, { dimColor: true, children: [" ", setupI18n.database[option.descKey]] })] }, option.value));
302
+ }) })] }));
303
+ }
304
+ // Render Turso URL input
305
+ if (step === 'turso-url') {
306
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: setupI18n.turso.urlPrompt }), _jsx(Text, { dimColor: true, children: setupI18n.turso.inputHint }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "\u203A " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleTursoUrlSubmit, placeholder: setupI18n.turso.urlPlaceholder })] }), inputError && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: inputError }) }))] }));
307
+ }
308
+ // Render Turso Token input
309
+ if (step === 'turso-token') {
310
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: setupI18n.turso.tokenPrompt }), _jsx(Text, { dimColor: true, children: setupI18n.turso.inputHint }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { children: "\u203A " }), _jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleTursoTokenSubmit, placeholder: "" })] }), inputError && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", children: inputError }) }))] }));
311
+ }
312
+ // Render Complete step
313
+ if (step === 'complete') {
314
+ const selectedTheme = themes[state.theme];
315
+ const dbLabel = state.useTurso
316
+ ? `Turso (${state.tursoUrl})`
317
+ : setupI18n.database.local;
318
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "green", children: setupI18n.complete.title }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { bold: true, children: setupI18n.complete.summary }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [_jsxs(Text, { children: [_jsxs(Text, { color: "cyan", children: [setupI18n.complete.language, ":"] }), " ", LOCALES.find(l => l.value === state.locale)?.label] }), _jsxs(Text, { children: [_jsxs(Text, { color: "cyan", children: [setupI18n.complete.theme, ":"] }), " ", selectedTheme.displayName] }), _jsxs(Text, { children: [_jsxs(Text, { color: "cyan", children: [setupI18n.complete.viewMode, ":"] }), " ", setupI18n.viewMode[state.viewMode === 'gtd' ? 'gtd' : 'kanban']] }), _jsxs(Text, { children: [_jsxs(Text, { color: "cyan", children: [setupI18n.complete.database, ":"] }), " ", dbLabel] })] }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { dimColor: true, children: setupI18n.complete.confirm }) })] }));
319
+ }
320
+ return _jsx(Text, { children: "Unknown step" });
321
+ }
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  interface HelpModalProps {
3
3
  onClose: () => void;
4
+ isKanban?: boolean;
4
5
  }
5
- export declare function HelpModal({ onClose }: HelpModalProps): React.ReactElement;
6
+ export declare function HelpModal({ onClose, isKanban }: HelpModalProps): React.ReactElement;
6
7
  export {};
@@ -1,11 +1,162 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from 'ink';
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useMemo } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
3
4
  import { t } from '../../i18n/index.js';
4
5
  import { useTheme } from '../theme/index.js';
5
- export function HelpModal({ onClose }) {
6
+ import { parseChangelog } from '../../changelog.js';
7
+ import { loadConfig, isTursoEnabled, getTursoConfig, getDbPath } from '../../config.js';
8
+ import { CONFIG_FILE, DATA_DIR } from '../../paths.js';
9
+ const VISIBLE_LINES = 12;
10
+ export function HelpModal({ onClose, isKanban = false }) {
11
+ const [activeTab, setActiveTab] = useState('keybindings');
12
+ const [scrollOffset, setScrollOffset] = useState(0);
13
+ const i18n = t();
14
+ const theme = useTheme();
15
+ const help = i18n.tui.help;
16
+ // Get changelog items for scroll calculation
17
+ const changelogItems = useMemo(() => {
18
+ const changelog = parseChangelog();
19
+ const items = [];
20
+ for (const entry of changelog.entries.slice(0, 5)) {
21
+ items.push({ type: 'version', content: entry.version, date: entry.date });
22
+ for (const section of entry.sections) {
23
+ items.push({ type: 'section', content: section.type });
24
+ for (const item of section.items) {
25
+ items.push({ type: 'item', content: item });
26
+ }
27
+ }
28
+ }
29
+ return items;
30
+ }, []);
31
+ const maxScroll = Math.max(0, changelogItems.length - VISIBLE_LINES);
32
+ const tabs = ['keybindings', 'info', 'whatsNew'];
33
+ useInput((input, key) => {
34
+ // Tab key detection (key.tab or raw tab character)
35
+ if (key.tab || input === '\t') {
36
+ setActiveTab(prev => {
37
+ const currentIndex = tabs.indexOf(prev);
38
+ return tabs[(currentIndex + 1) % tabs.length];
39
+ });
40
+ setScrollOffset(0);
41
+ return;
42
+ }
43
+ // Number keys for direct tab access
44
+ if (input === '1') {
45
+ setActiveTab('keybindings');
46
+ setScrollOffset(0);
47
+ return;
48
+ }
49
+ if (input === '2') {
50
+ setActiveTab('info');
51
+ setScrollOffset(0);
52
+ return;
53
+ }
54
+ if (input === '3') {
55
+ setActiveTab('whatsNew');
56
+ setScrollOffset(0);
57
+ return;
58
+ }
59
+ // Arrow keys and h/l for tab switching
60
+ if (input === 'h' || key.leftArrow) {
61
+ setActiveTab(prev => {
62
+ const currentIndex = tabs.indexOf(prev);
63
+ return tabs[(currentIndex - 1 + tabs.length) % tabs.length];
64
+ });
65
+ setScrollOffset(0);
66
+ return;
67
+ }
68
+ if (input === 'l' || key.rightArrow) {
69
+ setActiveTab(prev => {
70
+ const currentIndex = tabs.indexOf(prev);
71
+ return tabs[(currentIndex + 1) % tabs.length];
72
+ });
73
+ setScrollOffset(0);
74
+ return;
75
+ }
76
+ // Scroll in What's New tab
77
+ if (activeTab === 'whatsNew') {
78
+ if (input === 'j' || key.downArrow) {
79
+ setScrollOffset(prev => Math.min(prev + 1, maxScroll));
80
+ return;
81
+ }
82
+ if (input === 'k' || key.upArrow) {
83
+ setScrollOffset(prev => Math.max(prev - 1, 0));
84
+ return;
85
+ }
86
+ }
87
+ // Close modal
88
+ if (key.escape || key.return || input === 'q' || input === ' ') {
89
+ onClose();
90
+ }
91
+ });
92
+ const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
93
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.modal, borderColor: theme.colors.borderActive, paddingX: 2, paddingY: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsxs(Box, { children: [_jsxs(Text, { bold: activeTab === 'keybindings', color: activeTab === 'keybindings' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'keybindings', children: [' ', formatTitle(help.keybindingsTab), ' '] }), _jsx(Text, { color: theme.colors.textMuted, children: " \u2502 " }), _jsxs(Text, { bold: activeTab === 'info', color: activeTab === 'info' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'info', children: [' ', formatTitle(help.infoTab), ' '] }), _jsx(Text, { color: theme.colors.textMuted, children: " \u2502 " }), _jsxs(Text, { bold: activeTab === 'whatsNew', color: activeTab === 'whatsNew' ? theme.colors.secondary : theme.colors.textMuted, inverse: activeTab === 'whatsNew', children: [' ', formatTitle(help.whatsNewTab), ' '] })] }) }), activeTab === 'keybindings' ? (isKanban ? (_jsx(KanbanKeybindingsContent, {})) : (_jsx(GTDKeybindingsContent, {}))) : activeTab === 'info' ? (_jsx(InfoContent, {})) : (_jsx(WhatsNewContent, { items: changelogItems, scrollOffset: scrollOffset, visibleLines: VISIBLE_LINES, maxScroll: maxScroll })), _jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: activeTab === 'whatsNew' && maxScroll > 0
94
+ ? `j/k: scroll | ${help.tabHint} | ${help.closeHint}`
95
+ : `${help.tabHint} | ${help.closeHint}` }) })] }));
96
+ }
97
+ function GTDKeybindingsContent() {
6
98
  const i18n = t();
7
99
  const help = i18n.tui.help;
8
100
  const theme = useTheme();
9
101
  const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
10
- return (_jsxs(Box, { flexDirection: "column", borderStyle: theme.borders.modal, borderColor: theme.colors.borderActive, paddingX: 2, paddingY: 1, children: [_jsx(Box, { justifyContent: "center", marginBottom: 1, children: _jsx(Text, { bold: true, color: theme.colors.secondary, children: formatTitle(help.title) }) }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-5" }), " ", help.tabSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.prevNextTab] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "n" }), " ", help.moveToNext] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "s" }), " ", help.moveToSomeday] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "i" }), " ", help.moveToInbox] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "r" }), " ", help.refresh] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.projects) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "p" }), " ", help.makeProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "P" }), " ", help.linkToProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.openProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Esc/b" }), " ", help.backFromProject] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] }), _jsx(Box, { justifyContent: "center", marginTop: 1, children: _jsx(Text, { color: theme.colors.textMuted, children: help.closeHint }) })] }));
102
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-6" }), " ", help.tabSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.prevNextTab] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "n" }), " ", help.moveToNext] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "s" }), " ", help.moveToSomeday] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "w" }), " ", help.moveToWaiting] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "i" }), " ", help.moveToInbox] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "r" }), " ", help.refresh] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.projects) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "p" }), " ", help.makeProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "P" }), " ", help.linkToProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.openProject] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Esc/b" }), " ", help.backFromProject] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
103
+ }
104
+ function KanbanKeybindingsContent() {
105
+ const i18n = t();
106
+ const help = i18n.tui.kanbanHelp;
107
+ const theme = useTheme();
108
+ const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
109
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.navigation) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "h/l \u2190/\u2192" }), " ", help.columnSwitch] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "1-3" }), " ", help.columnDirect] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "j/k \u2191/\u2193" }), " ", help.taskSelect] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.actions) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "a" }), " ", help.addTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "d" }), " ", help.completeTask] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "Enter" }), " ", help.moveRight] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "BS" }), " ", help.moveLeft] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(help.other) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "/" }), " ", help.searchTasks] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "?" }), " ", help.showHelp] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.textHighlight, children: "q" }), " ", help.quit] })] })] })] }));
110
+ }
111
+ function InfoContent() {
112
+ const i18n = t();
113
+ const info = i18n.tui.info;
114
+ const theme = useTheme();
115
+ const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
116
+ const config = loadConfig();
117
+ const tursoEnabled = isTursoEnabled();
118
+ const tursoConfig = tursoEnabled ? getTursoConfig() : undefined;
119
+ const dbPath = getDbPath();
120
+ // Get Turso host for display
121
+ const tursoUrl = tursoConfig ? (() => {
122
+ try {
123
+ return new URL(tursoConfig.url).host;
124
+ }
125
+ catch {
126
+ return tursoConfig.url;
127
+ }
128
+ })() : '';
129
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(info.settings) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.theme, ":"] }), " ", config.theme] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.language, ":"] }), " ", config.locale] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.viewMode, ":"] }), " ", config.viewMode] })] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(info.database) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.dbType, ":"] }), " ", tursoEnabled ? info.turso : info.local] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.dbPath, ":"] }), " ", dbPath] }), tursoEnabled && tursoUrl && (_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.tursoUrl, ":"] }), " ", tursoUrl] }))] })] }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, color: theme.colors.accent, children: formatTitle(info.paths) }), _jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.configFile, ":"] }), " ", CONFIG_FILE] }), _jsxs(Text, { color: theme.colors.text, children: [_jsxs(Text, { color: theme.colors.textHighlight, children: [info.dataDir, ":"] }), " ", DATA_DIR] })] })] })] }));
130
+ }
131
+ function WhatsNewContent({ items, scrollOffset, visibleLines, maxScroll }) {
132
+ const i18n = t();
133
+ const whatsNew = i18n.tui.whatsNew;
134
+ const theme = useTheme();
135
+ const formatTitle = (title) => theme.style.headerUppercase ? title.toUpperCase() : title;
136
+ const getSectionLabel = (type) => {
137
+ const labels = {
138
+ Added: whatsNew.added,
139
+ Changed: whatsNew.changed,
140
+ Deprecated: whatsNew.deprecated,
141
+ Removed: whatsNew.removed,
142
+ Fixed: whatsNew.fixed,
143
+ Security: whatsNew.security,
144
+ };
145
+ return labels[type] || type;
146
+ };
147
+ if (items.length === 0) {
148
+ return (_jsx(Box, { justifyContent: "center", paddingY: 2, children: _jsx(Text, { color: theme.colors.textMuted, children: whatsNew.noChanges }) }));
149
+ }
150
+ const visibleItems = items.slice(scrollOffset, scrollOffset + visibleLines);
151
+ const showScrollUp = scrollOffset > 0;
152
+ const showScrollDown = scrollOffset < maxScroll;
153
+ return (_jsxs(Box, { flexDirection: "column", children: [showScrollUp && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25B2 scroll up" })), visibleItems.map((item, index) => {
154
+ if (item.type === 'version') {
155
+ return (_jsxs(Text, { bold: true, color: theme.colors.accent, children: ["v", item.content, " ", item.date && _jsxs(Text, { color: theme.colors.textMuted, children: ["(", item.date, ")"] })] }, `${item.content}-${index}`));
156
+ }
157
+ if (item.type === 'section') {
158
+ return (_jsxs(Text, { color: theme.colors.secondary, children: [' ', formatTitle(getSectionLabel(item.content)), ":"] }, `${item.content}-${index}`));
159
+ }
160
+ return (_jsxs(Text, { color: theme.colors.text, children: [' ', "\u2022 ", item.content] }, `${item.content}-${index}`));
161
+ }), showScrollDown && (_jsx(Text, { color: theme.colors.textMuted, children: " \u25BC scroll down" }))] }));
11
162
  }
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface KanbanBoardProps {
3
+ onSwitchToGtd?: () => void;
4
+ }
5
+ export declare function KanbanBoard({ onSwitchToGtd }: KanbanBoardProps): React.ReactElement;
6
+ export {};