@allpepper/task-orchestrator-tui 1.0.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.
Files changed (50) hide show
  1. package/README.md +78 -0
  2. package/package.json +54 -0
  3. package/src/tui/app.tsx +308 -0
  4. package/src/tui/components/column-filter-bar.tsx +52 -0
  5. package/src/tui/components/confirm-dialog.tsx +45 -0
  6. package/src/tui/components/dependency-list.tsx +115 -0
  7. package/src/tui/components/empty-state.tsx +28 -0
  8. package/src/tui/components/entity-table.tsx +120 -0
  9. package/src/tui/components/error-message.tsx +41 -0
  10. package/src/tui/components/feature-kanban-card.tsx +216 -0
  11. package/src/tui/components/footer.tsx +34 -0
  12. package/src/tui/components/form-dialog.tsx +338 -0
  13. package/src/tui/components/header.tsx +54 -0
  14. package/src/tui/components/index.ts +16 -0
  15. package/src/tui/components/kanban-board.tsx +335 -0
  16. package/src/tui/components/kanban-card.tsx +70 -0
  17. package/src/tui/components/kanban-column.tsx +173 -0
  18. package/src/tui/components/priority-badge.tsx +16 -0
  19. package/src/tui/components/section-list.tsx +96 -0
  20. package/src/tui/components/status-actions.tsx +87 -0
  21. package/src/tui/components/status-badge.tsx +22 -0
  22. package/src/tui/components/tree-view.tsx +295 -0
  23. package/src/tui/components/view-mode-chips.tsx +23 -0
  24. package/src/tui/index.tsx +33 -0
  25. package/src/tui/screens/dashboard.tsx +248 -0
  26. package/src/tui/screens/feature-detail.tsx +312 -0
  27. package/src/tui/screens/index.ts +6 -0
  28. package/src/tui/screens/kanban-view.tsx +251 -0
  29. package/src/tui/screens/project-detail.tsx +305 -0
  30. package/src/tui/screens/project-view.tsx +498 -0
  31. package/src/tui/screens/search.tsx +257 -0
  32. package/src/tui/screens/task-detail.tsx +294 -0
  33. package/src/ui/adapters/direct.ts +429 -0
  34. package/src/ui/adapters/index.ts +14 -0
  35. package/src/ui/adapters/types.ts +269 -0
  36. package/src/ui/context/adapter-context.tsx +31 -0
  37. package/src/ui/context/theme-context.tsx +43 -0
  38. package/src/ui/hooks/index.ts +20 -0
  39. package/src/ui/hooks/use-data.ts +919 -0
  40. package/src/ui/hooks/use-debounce.ts +37 -0
  41. package/src/ui/hooks/use-feature-kanban.ts +151 -0
  42. package/src/ui/hooks/use-kanban.ts +96 -0
  43. package/src/ui/hooks/use-navigation.tsx +94 -0
  44. package/src/ui/index.ts +73 -0
  45. package/src/ui/lib/colors.ts +79 -0
  46. package/src/ui/lib/format.ts +114 -0
  47. package/src/ui/lib/types.ts +157 -0
  48. package/src/ui/themes/dark.ts +63 -0
  49. package/src/ui/themes/light.ts +63 -0
  50. package/src/ui/themes/types.ts +71 -0
@@ -0,0 +1,248 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { useProjects } from '../../ui/hooks/use-data';
4
+ import { useAdapter } from '../../ui/context/adapter-context';
5
+ import { useTheme } from '../../ui/context/theme-context';
6
+ import { EntityTable } from '../components/entity-table';
7
+ import { StatusBadge } from '../components/status-badge';
8
+ import { timeAgo } from '../../ui/lib/format';
9
+ import type { ProjectWithCounts } from '../../ui/hooks/use-data';
10
+ import { ConfirmDialog } from '../components/confirm-dialog';
11
+ import { ErrorMessage } from '../components/error-message';
12
+ import { EmptyState } from '../components/empty-state';
13
+ import { FormDialog } from '../components/form-dialog';
14
+ import type { ProjectStatus } from 'task-orchestrator-bun/src/domain/types';
15
+
16
+ interface DashboardProps {
17
+ selectedIndex: number;
18
+ onSelectedIndexChange: (index: number) => void;
19
+ onSelectProject: (projectId: string) => void;
20
+ onViewProject: (projectId: string) => void;
21
+ onBack?: () => void;
22
+ }
23
+
24
+ export function Dashboard({ selectedIndex, onSelectedIndexChange, onSelectProject, onViewProject, onBack }: DashboardProps) {
25
+ const { adapter } = useAdapter();
26
+ const { theme } = useTheme();
27
+ const { projects, loading, error, refresh } = useProjects();
28
+ const [mode, setMode] = useState<'idle' | 'create' | 'edit' | 'delete' | 'status'>('idle');
29
+ const [localError, setLocalError] = useState<string | null>(null);
30
+ const [allowedTransitions, setAllowedTransitions] = useState<string[]>([]);
31
+ const [statusIndex, setStatusIndex] = useState(0);
32
+
33
+ const columns = [
34
+ {
35
+ key: 'name',
36
+ label: 'Name',
37
+ width: 50,
38
+ },
39
+ {
40
+ key: 'status',
41
+ label: 'Status',
42
+ width: 20,
43
+ render: (_value: unknown, row: ProjectWithCounts, context?: { isSelected: boolean }) => (
44
+ <StatusBadge status={row.status} isSelected={context?.isSelected} />
45
+ ),
46
+ },
47
+ {
48
+ key: 'tasks',
49
+ label: 'Tasks',
50
+ width: 15,
51
+ render: (_value: unknown, row: ProjectWithCounts) =>
52
+ `${row.taskCounts.completed}/${row.taskCounts.total}`,
53
+ },
54
+ {
55
+ key: 'modifiedAt',
56
+ label: 'Modified',
57
+ width: 15,
58
+ render: (_value: unknown, row: ProjectWithCounts) => timeAgo(row.modifiedAt),
59
+ },
60
+ ];
61
+
62
+ // Clamp selectedIndex if data changed
63
+ const clampedSelectedIndex = Math.min(selectedIndex, Math.max(0, projects.length - 1));
64
+ const effectiveSelectedIndex = projects.length > 0 ? clampedSelectedIndex : 0;
65
+ const selectedProject = projects[effectiveSelectedIndex];
66
+
67
+ const statusTargets = useMemo(() => allowedTransitions as ProjectStatus[], [allowedTransitions]);
68
+
69
+ useInput((input, key) => {
70
+ // Handle status mode
71
+ if (mode === 'status') {
72
+ if (input === 'j' || key.downArrow) {
73
+ setStatusIndex((prev) => (prev + 1) % Math.max(1, statusTargets.length));
74
+ return;
75
+ }
76
+ if (input === 'k' || key.upArrow) {
77
+ setStatusIndex((prev) => (prev - 1 + Math.max(1, statusTargets.length)) % Math.max(1, statusTargets.length));
78
+ return;
79
+ }
80
+ if (key.escape) {
81
+ setMode('idle');
82
+ return;
83
+ }
84
+ if (key.return && selectedProject && statusTargets[statusIndex]) {
85
+ const next = statusTargets[statusIndex];
86
+ adapter.setProjectStatus(selectedProject.id, next, selectedProject.version).then((result) => {
87
+ if (!result.success) {
88
+ setLocalError(result.error);
89
+ }
90
+ refresh();
91
+ setMode('idle');
92
+ });
93
+ }
94
+ return;
95
+ }
96
+
97
+ // Handle idle mode
98
+ if (mode === 'idle') {
99
+ if (input === 'n') {
100
+ setMode('create');
101
+ return;
102
+ }
103
+ if (input === 'e' && selectedProject) {
104
+ setMode('edit');
105
+ return;
106
+ }
107
+ if (input === 'f' && selectedProject) {
108
+ onViewProject(selectedProject.id);
109
+ return;
110
+ }
111
+ if (input === 'd' && selectedProject) {
112
+ setMode('delete');
113
+ return;
114
+ }
115
+ if (input === 's' && selectedProject) {
116
+ adapter.getAllowedTransitions('PROJECT', selectedProject.status).then((result) => {
117
+ if (result.success) {
118
+ setAllowedTransitions(result.data);
119
+ setMode('status');
120
+ }
121
+ });
122
+ return;
123
+ }
124
+ if (input === 'r') {
125
+ refresh();
126
+ }
127
+ if (key.escape && mode !== 'idle') {
128
+ setMode('idle');
129
+ }
130
+ }
131
+ });
132
+
133
+ return (
134
+ <Box paddingX={1} paddingY={1} flexDirection="column">
135
+ {loading ? (
136
+ <Text>Loading projects...</Text>
137
+ ) : null}
138
+ {!loading && error && !localError ? (
139
+ <Text color={theme.colors.danger}>Error: {error}</Text>
140
+ ) : null}
141
+
142
+ {localError ? (
143
+ <ErrorMessage message={localError} onDismiss={() => setLocalError(null)} />
144
+ ) : null}
145
+
146
+ {!loading && (!error || localError) ? (
147
+ projects.length === 0 && mode === 'idle' ? (
148
+ <EmptyState message="No projects found." hint="Press n to create a project." />
149
+ ) : (
150
+ <EntityTable
151
+ columns={columns}
152
+ data={projects}
153
+ selectedIndex={effectiveSelectedIndex}
154
+ onSelectedIndexChange={onSelectedIndexChange}
155
+ onSelect={(project) => onSelectProject(project.id)}
156
+ onBack={onBack}
157
+ isActive={mode === 'idle'}
158
+ />
159
+ )
160
+ ) : null}
161
+
162
+ {mode === 'create' ? (
163
+ <FormDialog
164
+ title="Create Project"
165
+ fields={[
166
+ { key: 'name', label: 'Name', required: true },
167
+ { key: 'summary', label: 'Summary', required: true },
168
+ { key: 'description', label: 'Description' },
169
+ ]}
170
+ onCancel={() => setMode('idle')}
171
+ onSubmit={(values) => {
172
+ adapter.createProject({
173
+ name: values.name ?? '',
174
+ summary: values.summary ?? '',
175
+ description: values.description || undefined,
176
+ }).then((result) => {
177
+ if (!result.success) {
178
+ setLocalError(result.error);
179
+ }
180
+ refresh();
181
+ setMode('idle');
182
+ });
183
+ }}
184
+ />
185
+ ) : null}
186
+
187
+ {mode === 'edit' && selectedProject ? (
188
+ <FormDialog
189
+ title="Edit Project"
190
+ fields={[
191
+ { key: 'name', label: 'Name', required: true, value: selectedProject.name },
192
+ { key: 'summary', label: 'Summary', required: true, value: selectedProject.summary },
193
+ { key: 'description', label: 'Description', value: selectedProject.description ?? '' },
194
+ ]}
195
+ onCancel={() => setMode('idle')}
196
+ onSubmit={(values) => {
197
+ adapter.updateProject(selectedProject.id, {
198
+ name: values.name ?? '',
199
+ summary: values.summary ?? '',
200
+ description: values.description || undefined,
201
+ version: selectedProject.version,
202
+ }).then((result) => {
203
+ if (!result.success) {
204
+ setLocalError(result.error);
205
+ }
206
+ refresh();
207
+ setMode('idle');
208
+ });
209
+ }}
210
+ />
211
+ ) : null}
212
+
213
+ {mode === 'delete' && selectedProject ? (
214
+ <ConfirmDialog
215
+ title="Delete Project"
216
+ message={`Delete "${selectedProject.name}"?`}
217
+ onCancel={() => setMode('idle')}
218
+ onConfirm={() => {
219
+ adapter.deleteProject(selectedProject.id).then((result) => {
220
+ if (!result.success) {
221
+ setLocalError(result.error);
222
+ }
223
+ refresh();
224
+ setMode('idle');
225
+ });
226
+ }}
227
+ />
228
+ ) : null}
229
+
230
+ {mode === 'status' && selectedProject ? (
231
+ <Box flexDirection="column" borderStyle="round" borderColor={theme.colors.highlight} paddingX={1} marginTop={1}>
232
+ <Text bold>Set Project Status</Text>
233
+ {statusTargets.length === 0 ? (
234
+ <Text dimColor>No transitions available</Text>
235
+ ) : (
236
+ statusTargets.map((status, idx) => (
237
+ <Text key={status} inverse={idx === statusIndex}>
238
+ {idx === statusIndex ? '>' : ' '} {status}
239
+ </Text>
240
+ ))
241
+ )}
242
+ <Text dimColor>Enter apply • Esc cancel</Text>
243
+ </Box>
244
+ ) : null}
245
+
246
+ </Box>
247
+ );
248
+ }
@@ -0,0 +1,312 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { useAdapter } from '../../ui/context/adapter-context';
4
+ import { useFeature } from '../../ui/hooks/use-data';
5
+ import type { FeatureStatus, Priority } from 'task-orchestrator-bun/src/domain/types';
6
+ import { StatusBadge } from '../components/status-badge';
7
+ import { PriorityBadge } from '../components/priority-badge';
8
+ import { SectionList } from '../components/section-list';
9
+ import { timeAgo } from '../../ui/lib/format';
10
+ import { FormDialog } from '../components/form-dialog';
11
+ import { ErrorMessage } from '../components/error-message';
12
+ import { EmptyState } from '../components/empty-state';
13
+ import { useTheme } from '../../ui/context/theme-context';
14
+
15
+ interface FeatureDetailProps {
16
+ featureId: string;
17
+ onSelectTask: (taskId: string) => void;
18
+ onBack: () => void;
19
+ }
20
+
21
+ export function FeatureDetail({ featureId, onSelectTask, onBack }: FeatureDetailProps) {
22
+ const { adapter } = useAdapter();
23
+ const { theme } = useTheme();
24
+ const { feature, tasks, sections, loading, error, refresh } = useFeature(featureId);
25
+ const [selectedTaskIndex, setSelectedTaskIndex] = useState(0);
26
+ const [selectedSectionIndex, setSelectedSectionIndex] = useState(0);
27
+ const [mode, setMode] = useState<'idle' | 'edit-feature' | 'create-task' | 'feature-status'>('idle');
28
+ const [localError, setLocalError] = useState<string | null>(null);
29
+ const [transitions, setTransitions] = useState<string[]>([]);
30
+ const [transitionIndex, setTransitionIndex] = useState(0);
31
+
32
+ // Handle keyboard navigation
33
+ useInput((input, key) => {
34
+ if (mode !== 'idle') return;
35
+ if (key.escape || input === 'h' || key.leftArrow) {
36
+ onBack();
37
+ }
38
+ if (input === 'r') {
39
+ refresh();
40
+ }
41
+ if (input === 'e' && feature) {
42
+ setMode('edit-feature');
43
+ }
44
+ if (input === 'n' && feature) {
45
+ setMode('create-task');
46
+ }
47
+ if (input === 's' && feature) {
48
+ adapter.getAllowedTransitions('FEATURE', feature.status).then((result) => {
49
+ if (result.success) {
50
+ setTransitions(result.data);
51
+ setTransitionIndex(0);
52
+ setMode('feature-status');
53
+ }
54
+ });
55
+ }
56
+ if (tasks.length > 0) {
57
+ if (input === 'j' || key.downArrow) {
58
+ setSelectedTaskIndex((prev) => Math.min(prev + 1, tasks.length - 1));
59
+ }
60
+ if (input === 'k' || key.upArrow) {
61
+ setSelectedTaskIndex((prev) => Math.max(prev - 1, 0));
62
+ }
63
+ if (key.return) {
64
+ const selectedTask = tasks[selectedTaskIndex];
65
+ if (selectedTask) {
66
+ onSelectTask(selectedTask.id);
67
+ }
68
+ }
69
+ }
70
+ });
71
+
72
+ useInput((input, key) => {
73
+ if (mode !== 'feature-status' || !feature) return;
74
+ if (input === 'j' || key.downArrow) {
75
+ setTransitionIndex((prev) => (prev + 1) % Math.max(1, transitions.length));
76
+ return;
77
+ }
78
+ if (input === 'k' || key.upArrow) {
79
+ setTransitionIndex((prev) => (prev - 1 + Math.max(1, transitions.length)) % Math.max(1, transitions.length));
80
+ return;
81
+ }
82
+ if (key.escape) {
83
+ setMode('idle');
84
+ return;
85
+ }
86
+ if (key.return) {
87
+ const nextStatus = transitions[transitionIndex] as FeatureStatus | undefined;
88
+ if (!nextStatus) return;
89
+ adapter.setFeatureStatus(feature.id, nextStatus, feature.version).then((result) => {
90
+ if (!result.success) setLocalError(result.error);
91
+ refresh();
92
+ setMode('idle');
93
+ });
94
+ }
95
+ }, { isActive: mode === 'feature-status' });
96
+
97
+ if (loading) {
98
+ return (
99
+ <Box padding={1}>
100
+ <Text>Loading feature...</Text>
101
+ </Box>
102
+ );
103
+ }
104
+
105
+ if (error) {
106
+ return (
107
+ <Box padding={1}>
108
+ <Text color={theme.colors.danger}>Error: {error}</Text>
109
+ </Box>
110
+ );
111
+ }
112
+
113
+ if (!feature) {
114
+ return (
115
+ <Box padding={1}>
116
+ <Text>Feature not found</Text>
117
+ </Box>
118
+ );
119
+ }
120
+
121
+ return (
122
+ <Box flexDirection="column" padding={1}>
123
+ {/* Feature Header */}
124
+ <Box marginBottom={1}>
125
+ <Text bold>{feature.name}</Text>
126
+ <Text> </Text>
127
+ <StatusBadge status={feature.status} />
128
+ </Box>
129
+
130
+ {/* Divider */}
131
+ <Box marginY={0}>
132
+ <Text dimColor>{'─'.repeat(40)}</Text>
133
+ </Box>
134
+
135
+ {/* Feature Metadata */}
136
+ <Box marginBottom={1}>
137
+ <Text>Priority: </Text>
138
+ <PriorityBadge priority={feature.priority} />
139
+ <Text> Modified: </Text>
140
+ <Text dimColor>{timeAgo(new Date(feature.modifiedAt))}</Text>
141
+ </Box>
142
+
143
+ {/* Divider */}
144
+ <Box marginY={0}>
145
+ <Text dimColor>{'─'.repeat(40)}</Text>
146
+ </Box>
147
+
148
+ {/* Feature Details (summary) */}
149
+ <Box flexDirection="column" marginBottom={1}>
150
+ <Text bold>Details</Text>
151
+ <Box marginLeft={1}>
152
+ <Text wrap="wrap">{feature.summary}</Text>
153
+ </Box>
154
+ </Box>
155
+
156
+ {/* Divider */}
157
+ {feature.description && (
158
+ <Box marginY={0}>
159
+ <Text dimColor>{'─'.repeat(40)}</Text>
160
+ </Box>
161
+ )}
162
+
163
+ {/* Feature Description */}
164
+ {feature.description && (
165
+ <Box flexDirection="column" marginBottom={1}>
166
+ <Text bold>Description</Text>
167
+ <Box marginLeft={1}>
168
+ <Text wrap="wrap">{feature.description}</Text>
169
+ </Box>
170
+ </Box>
171
+ )}
172
+
173
+ {/* Divider */}
174
+ <Box marginY={0}>
175
+ <Text dimColor>{'─'.repeat(40)}</Text>
176
+ </Box>
177
+
178
+ {/* Tasks List */}
179
+ <Box flexDirection="column" marginBottom={1}>
180
+ <Text bold>Tasks ({tasks.length})</Text>
181
+ {tasks.length === 0 ? (
182
+ <Box marginLeft={1}><EmptyState message="No tasks" hint="Press n to create one." /></Box>
183
+ ) : (
184
+ <Box flexDirection="column" marginLeft={1}>
185
+ {tasks.map((task, index) => {
186
+ const isSelected = index === selectedTaskIndex;
187
+ return (
188
+ <Box key={task.id}>
189
+ <Text color={isSelected ? theme.colors.highlight : undefined}>
190
+ {isSelected ? '▎' : ' '}
191
+ </Text>
192
+ <Text> </Text>
193
+ <StatusBadge status={task.status} />
194
+ <Text> </Text>
195
+ <Text bold={isSelected}>
196
+ {task.title}
197
+ </Text>
198
+ </Box>
199
+ );
200
+ })}
201
+ </Box>
202
+ )}
203
+ </Box>
204
+
205
+ {/* Divider */}
206
+ {sections.length > 0 && (
207
+ <Box marginY={0}>
208
+ <Text dimColor>{'─'.repeat(40)}</Text>
209
+ </Box>
210
+ )}
211
+
212
+ {/* Sections Panel - only show if there are sections */}
213
+ {sections.length > 0 && (
214
+ <Box flexDirection="column" marginBottom={1}>
215
+ <Text bold>Sections</Text>
216
+ <SectionList
217
+ sections={sections}
218
+ selectedIndex={selectedSectionIndex}
219
+ onSelectedIndexChange={setSelectedSectionIndex}
220
+ isActive={true}
221
+ />
222
+ </Box>
223
+ )}
224
+
225
+ {/* Help Footer */}
226
+ <Box marginTop={1}>
227
+ <Text dimColor>
228
+ ESC/h: Back | r: Refresh | n: New Task | e: Edit Feature | s: Feature Status{tasks.length > 0 ? ' | j/k: Navigate | Enter: Select Task' : ''}
229
+ </Text>
230
+ </Box>
231
+
232
+ {localError ? <ErrorMessage message={localError} onDismiss={() => setLocalError(null)} /> : null}
233
+
234
+ {mode === 'edit-feature' ? (
235
+ <FormDialog
236
+ title="Edit Feature"
237
+ fields={[
238
+ { key: 'name', label: 'Name', required: true, value: feature.name },
239
+ { key: 'summary', label: 'Summary', required: true, value: feature.summary },
240
+ { key: 'description', label: 'Description', value: feature.description ?? '' },
241
+ { key: 'priority', label: 'Priority (HIGH/MEDIUM/LOW)', required: true, value: feature.priority },
242
+ ]}
243
+ onCancel={() => setMode('idle')}
244
+ onSubmit={(values) => {
245
+ adapter.updateFeature(feature.id, {
246
+ name: values.name ?? '',
247
+ summary: values.summary ?? '',
248
+ description: values.description || undefined,
249
+ priority: ((values.priority ?? feature.priority) as Priority),
250
+ version: feature.version,
251
+ }).then((result) => {
252
+ if (!result.success) setLocalError(result.error);
253
+ refresh();
254
+ setMode('idle');
255
+ });
256
+ }}
257
+ />
258
+ ) : null}
259
+
260
+ {mode === 'create-task' ? (
261
+ <FormDialog
262
+ title="Create Task"
263
+ fields={[
264
+ { key: 'title', label: 'Title', required: true },
265
+ { key: 'summary', label: 'Summary', required: true },
266
+ { key: 'description', label: 'Description' },
267
+ { key: 'priority', label: 'Priority (HIGH/MEDIUM/LOW)', required: true, value: 'MEDIUM' },
268
+ { key: 'complexity', label: 'Complexity (1-10)', required: true, value: '3' },
269
+ ]}
270
+ onCancel={() => setMode('idle')}
271
+ onSubmit={(values) => {
272
+ adapter.createTask({
273
+ projectId: feature.projectId,
274
+ featureId: feature.id,
275
+ title: values.title ?? '',
276
+ summary: values.summary ?? '',
277
+ description: values.description || undefined,
278
+ priority: ((values.priority ?? 'MEDIUM') as Priority),
279
+ complexity: Number.parseInt(values.complexity ?? '3', 10) || 3,
280
+ }).then((result) => {
281
+ if (!result.success) setLocalError(result.error);
282
+ refresh();
283
+ setMode('idle');
284
+ });
285
+ }}
286
+ />
287
+ ) : null}
288
+
289
+ {mode === 'feature-status' ? (
290
+ <Box flexDirection="column" borderStyle="round" borderColor={theme.colors.accent} paddingX={1} marginTop={1}>
291
+ <Text bold>Set Feature Status</Text>
292
+ {transitions.length === 0 ? (
293
+ <Text dimColor>No transitions available</Text>
294
+ ) : (
295
+ transitions.map((status, idx) => {
296
+ const isSelected = idx === transitionIndex;
297
+ return (
298
+ <Box key={status}>
299
+ <Text color={isSelected ? theme.colors.highlight : undefined}>
300
+ {isSelected ? '▎' : ' '}
301
+ </Text>
302
+ <Text bold={isSelected}> {status}</Text>
303
+ </Box>
304
+ );
305
+ })
306
+ )}
307
+ <Text dimColor>Enter apply • Esc cancel</Text>
308
+ </Box>
309
+ ) : null}
310
+ </Box>
311
+ );
312
+ }
@@ -0,0 +1,6 @@
1
+ export { Dashboard } from './dashboard';
2
+ export { ProjectView } from './project-view';
3
+ export { TaskDetail } from './task-detail';
4
+ export { FeatureDetail } from './feature-detail';
5
+ export { KanbanView } from './kanban-view';
6
+ export { SearchScreen } from './search';