@compilr-dev/cli 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +317 -0
- package/dist/agents/registry.d.ts +66 -0
- package/dist/agents/registry.js +238 -0
- package/dist/agents/types.d.ts +40 -0
- package/dist/agents/types.js +94 -0
- package/dist/commands/custom-registry.d.ts +69 -0
- package/dist/commands/custom-registry.js +246 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/types.d.ts +31 -0
- package/dist/commands/types.js +26 -0
- package/dist/commands.d.ts +63 -0
- package/dist/commands.js +324 -0
- package/dist/db/index.d.ts +42 -0
- package/dist/db/index.js +146 -0
- package/dist/db/repositories/document-repository.d.ts +63 -0
- package/dist/db/repositories/document-repository.js +184 -0
- package/dist/db/repositories/index.d.ts +9 -0
- package/dist/db/repositories/index.js +6 -0
- package/dist/db/repositories/project-repository.d.ts +132 -0
- package/dist/db/repositories/project-repository.js +337 -0
- package/dist/db/repositories/work-item-repository.d.ts +115 -0
- package/dist/db/repositories/work-item-repository.js +389 -0
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +143 -0
- package/dist/debug.d.ts +8 -0
- package/dist/debug.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +348 -0
- package/dist/index.old.d.ts +7 -0
- package/dist/index.old.js +1014 -0
- package/dist/repl.d.ts +121 -0
- package/dist/repl.js +1878 -0
- package/dist/settings/index.d.ts +80 -0
- package/dist/settings/index.js +195 -0
- package/dist/shared-handlers.d.ts +63 -0
- package/dist/shared-handlers.js +57 -0
- package/dist/slash-autocomplete.d.ts +41 -0
- package/dist/slash-autocomplete.js +638 -0
- package/dist/state.d.ts +75 -0
- package/dist/state.js +130 -0
- package/dist/tabbed-menu.d.ts +11 -0
- package/dist/tabbed-menu.js +328 -0
- package/dist/templates/backlog-md.d.ts +7 -0
- package/dist/templates/backlog-md.js +94 -0
- package/dist/templates/claude-md.d.ts +7 -0
- package/dist/templates/claude-md.js +189 -0
- package/dist/templates/coding-standards.d.ts +7 -0
- package/dist/templates/coding-standards.js +299 -0
- package/dist/templates/compilr-md.d.ts +7 -0
- package/dist/templates/compilr-md.js +189 -0
- package/dist/templates/config-json.d.ts +38 -0
- package/dist/templates/config-json.js +39 -0
- package/dist/templates/gitignore.d.ts +7 -0
- package/dist/templates/gitignore.js +85 -0
- package/dist/templates/index.d.ts +19 -0
- package/dist/templates/index.js +302 -0
- package/dist/templates/package-json.d.ts +7 -0
- package/dist/templates/package-json.js +111 -0
- package/dist/templates/readme-md.d.ts +7 -0
- package/dist/templates/readme-md.js +161 -0
- package/dist/templates/tsconfig.d.ts +7 -0
- package/dist/templates/tsconfig.js +61 -0
- package/dist/templates/types.d.ts +33 -0
- package/dist/templates/types.js +24 -0
- package/dist/test-autocomplete.d.ts +7 -0
- package/dist/test-autocomplete.js +85 -0
- package/dist/test-tabbed-menu.d.ts +7 -0
- package/dist/test-tabbed-menu.js +25 -0
- package/dist/themes/colors.d.ts +49 -0
- package/dist/themes/colors.js +135 -0
- package/dist/themes/index.d.ts +23 -0
- package/dist/themes/index.js +24 -0
- package/dist/themes/registry.d.ts +60 -0
- package/dist/themes/registry.js +195 -0
- package/dist/themes/types.d.ts +82 -0
- package/dist/themes/types.js +7 -0
- package/dist/tool-selector.d.ts +71 -0
- package/dist/tool-selector.js +184 -0
- package/dist/tools/ask-user-simple.d.ts +19 -0
- package/dist/tools/ask-user-simple.js +86 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +113 -0
- package/dist/tools/backlog.d.ts +53 -0
- package/dist/tools/backlog.js +709 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +121 -0
- package/dist/ui/agents-overlay.d.ts +12 -0
- package/dist/ui/agents-overlay.js +501 -0
- package/dist/ui/arch-type-overlay.d.ts +20 -0
- package/dist/ui/arch-type-overlay.js +229 -0
- package/dist/ui/ask-user-overlay.d.ts +26 -0
- package/dist/ui/ask-user-overlay.js +647 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay.js +242 -0
- package/dist/ui/backlog-overlay.d.ts +17 -0
- package/dist/ui/backlog-overlay.js +786 -0
- package/dist/ui/commands-overlay.d.ts +11 -0
- package/dist/ui/commands-overlay.js +410 -0
- package/dist/ui/config-overlay.d.ts +34 -0
- package/dist/ui/config-overlay.js +977 -0
- package/dist/ui/conversation.d.ts +82 -0
- package/dist/ui/conversation.js +508 -0
- package/dist/ui/diff.d.ts +38 -0
- package/dist/ui/diff.js +182 -0
- package/dist/ui/ephemeral.d.ts +111 -0
- package/dist/ui/ephemeral.js +413 -0
- package/dist/ui/file-autocomplete.d.ts +45 -0
- package/dist/ui/file-autocomplete.js +237 -0
- package/dist/ui/footer.d.ts +153 -0
- package/dist/ui/footer.js +422 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/init-overlay.d.ts +24 -0
- package/dist/ui/init-overlay.js +525 -0
- package/dist/ui/input-prompt-v2.d.ts +179 -0
- package/dist/ui/input-prompt-v2.js +991 -0
- package/dist/ui/input-prompt.d.ts +97 -0
- package/dist/ui/input-prompt.js +800 -0
- package/dist/ui/iteration-limit-overlay.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay.js +150 -0
- package/dist/ui/keys-overlay.d.ts +14 -0
- package/dist/ui/keys-overlay.js +181 -0
- package/dist/ui/model-warning-overlay.d.ts +30 -0
- package/dist/ui/model-warning-overlay.js +171 -0
- package/dist/ui/overlay-controller.d.ts +25 -0
- package/dist/ui/overlay-controller.js +35 -0
- package/dist/ui/overlays.d.ts +47 -0
- package/dist/ui/overlays.js +627 -0
- package/dist/ui/permission-overlay.d.ts +16 -0
- package/dist/ui/permission-overlay.js +494 -0
- package/dist/ui/terminal.d.ts +117 -0
- package/dist/ui/terminal.js +237 -0
- package/dist/ui/todo-zone.d.ts +112 -0
- package/dist/ui/todo-zone.js +353 -0
- package/dist/ui/tools-overlay.d.ts +26 -0
- package/dist/ui/tools-overlay.js +278 -0
- package/dist/ui/tutorial-overlay.d.ts +10 -0
- package/dist/ui/tutorial-overlay.js +936 -0
- package/dist/ui/types.d.ts +103 -0
- package/dist/ui/types.js +33 -0
- package/dist/utils/credentials.d.ts +55 -0
- package/dist/utils/credentials.js +268 -0
- package/dist/utils/model-tiers.d.ts +37 -0
- package/dist/utils/model-tiers.js +118 -0
- package/dist/utils/project-memory.d.ts +47 -0
- package/dist/utils/project-memory.js +117 -0
- package/dist/utils/project-status.d.ts +56 -0
- package/dist/utils/project-status.js +237 -0
- package/package.json +66 -0
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog Overlay
|
|
3
|
+
*
|
|
4
|
+
* Modal overlay for viewing and managing the project backlog.
|
|
5
|
+
* Provides a UI for manually adding, editing, and filtering backlog items.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Search by title/description
|
|
9
|
+
* - Filter tabs by type (All/Feature/Bug/Tech-Debt/Chore)
|
|
10
|
+
* - Pagination for large backlogs
|
|
11
|
+
* - Quick status toggle with Space
|
|
12
|
+
* - Add new items with wizard
|
|
13
|
+
*/
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as terminal from './terminal.js';
|
|
18
|
+
import { getStyles } from '../themes/index.js';
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// Constants
|
|
21
|
+
// =============================================================================
|
|
22
|
+
const PAGE_SIZE = 8;
|
|
23
|
+
const TYPE_OPTIONS = ['feature', 'bug', 'tech-debt', 'chore'];
|
|
24
|
+
const STATUS_OPTIONS = ['📋', '🚧', '✅'];
|
|
25
|
+
const PRIORITY_OPTIONS = ['critical', 'high', 'medium', 'low'];
|
|
26
|
+
const TYPE_LABELS = {
|
|
27
|
+
'feature': 'Feature',
|
|
28
|
+
'bug': 'Bug',
|
|
29
|
+
'tech-debt': 'Tech Debt',
|
|
30
|
+
'chore': 'Chore',
|
|
31
|
+
};
|
|
32
|
+
const PRIORITY_LABELS = {
|
|
33
|
+
'critical': 'Critical',
|
|
34
|
+
'high': 'High',
|
|
35
|
+
'medium': 'Medium',
|
|
36
|
+
'low': 'Low',
|
|
37
|
+
};
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Backlog File Operations
|
|
40
|
+
// =============================================================================
|
|
41
|
+
function findBacklogPath() {
|
|
42
|
+
const cwd = process.cwd();
|
|
43
|
+
// Single repo pattern: .compilr/backlog.md
|
|
44
|
+
const singleRepoPath = path.join(cwd, '.compilr', 'backlog.md');
|
|
45
|
+
if (fs.existsSync(singleRepoPath)) {
|
|
46
|
+
return singleRepoPath;
|
|
47
|
+
}
|
|
48
|
+
// Two repo pattern: look for -docs sibling folder
|
|
49
|
+
const parentDir = path.dirname(cwd);
|
|
50
|
+
const projectName = path.basename(cwd);
|
|
51
|
+
const docsRepoPath = path.join(parentDir, `${projectName}-docs`, '01-planning', 'backlog.md');
|
|
52
|
+
if (fs.existsSync(docsRepoPath)) {
|
|
53
|
+
return docsRepoPath;
|
|
54
|
+
}
|
|
55
|
+
// Check if we're in the docs repo itself
|
|
56
|
+
const inDocsPath = path.join(cwd, '01-planning', 'backlog.md');
|
|
57
|
+
if (fs.existsSync(inDocsPath)) {
|
|
58
|
+
return inDocsPath;
|
|
59
|
+
}
|
|
60
|
+
// Check for project subfolders with -docs pattern
|
|
61
|
+
try {
|
|
62
|
+
const entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (entry.isDirectory() && entry.name.endsWith('-docs')) {
|
|
65
|
+
const docsBacklog = path.join(cwd, entry.name, '01-planning', 'backlog.md');
|
|
66
|
+
if (fs.existsSync(docsBacklog)) {
|
|
67
|
+
return docsBacklog;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Ignore read errors
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function parseBacklogItems(content) {
|
|
78
|
+
const items = [];
|
|
79
|
+
const lines = content.split('\n');
|
|
80
|
+
let inTable = false;
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
if (trimmed.startsWith('| ID |')) {
|
|
84
|
+
inTable = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (trimmed.startsWith('|---')) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (inTable && trimmed.startsWith('|')) {
|
|
91
|
+
const cells = trimmed.split('|').map(c => c.trim()).filter(c => c !== '');
|
|
92
|
+
if (cells.length >= 6) {
|
|
93
|
+
const [id, type, status, priority, title, description, commit] = cells;
|
|
94
|
+
if (id && /^[A-Z]+-\d{3}$/.test(id)) {
|
|
95
|
+
items.push({
|
|
96
|
+
id,
|
|
97
|
+
type: type,
|
|
98
|
+
status: status,
|
|
99
|
+
priority: priority,
|
|
100
|
+
title,
|
|
101
|
+
description,
|
|
102
|
+
commit: commit || undefined,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else if (inTable && !trimmed.startsWith('|')) {
|
|
108
|
+
inTable = false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return items;
|
|
112
|
+
}
|
|
113
|
+
function generateBacklogMarkdown(items) {
|
|
114
|
+
const date = new Date().toISOString().split('T')[0];
|
|
115
|
+
let content = `# Backlog
|
|
116
|
+
|
|
117
|
+
| ID | Type | Status | Priority | Title | Description | Commit |
|
|
118
|
+
|----|------|--------|----------|-------|-------------|--------|
|
|
119
|
+
`;
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
content += `| ${item.id} | ${item.type} | ${item.status} | ${item.priority} | ${item.title} | ${item.description} | ${item.commit ?? ''} |\n`;
|
|
122
|
+
}
|
|
123
|
+
content += `
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
*Last updated: ${date} by user*
|
|
127
|
+
`;
|
|
128
|
+
return content;
|
|
129
|
+
}
|
|
130
|
+
function generateId(type, existingItems) {
|
|
131
|
+
const prefixMap = {
|
|
132
|
+
'feature': 'REQ',
|
|
133
|
+
'bug': 'BUG',
|
|
134
|
+
'tech-debt': 'TECH',
|
|
135
|
+
'chore': 'CHORE',
|
|
136
|
+
};
|
|
137
|
+
const prefix = prefixMap[type];
|
|
138
|
+
let maxNum = 0;
|
|
139
|
+
for (const item of existingItems) {
|
|
140
|
+
if (item.id.startsWith(prefix + '-')) {
|
|
141
|
+
const num = parseInt(item.id.split('-')[1], 10);
|
|
142
|
+
if (!isNaN(num) && num > maxNum) {
|
|
143
|
+
maxNum = num;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return `${prefix}-${String(maxNum + 1).padStart(3, '0')}`;
|
|
148
|
+
}
|
|
149
|
+
// =============================================================================
|
|
150
|
+
// Filtering & Search
|
|
151
|
+
// =============================================================================
|
|
152
|
+
function updateFilteredItems(state) {
|
|
153
|
+
let results = [...state.items];
|
|
154
|
+
// Apply type filter
|
|
155
|
+
if (state.filterType === 'active') {
|
|
156
|
+
// Active = not completed (📋 or 🚧)
|
|
157
|
+
results = results.filter(item => item.status === '📋' || item.status === '🚧');
|
|
158
|
+
}
|
|
159
|
+
else if (state.filterType !== 'all') {
|
|
160
|
+
results = results.filter(item => item.type === state.filterType);
|
|
161
|
+
}
|
|
162
|
+
// Apply search query
|
|
163
|
+
if (state.searchQuery.trim()) {
|
|
164
|
+
const query = state.searchQuery.toLowerCase();
|
|
165
|
+
results = results.filter(item => item.title.toLowerCase().includes(query) ||
|
|
166
|
+
item.description.toLowerCase().includes(query) ||
|
|
167
|
+
item.id.toLowerCase().includes(query));
|
|
168
|
+
}
|
|
169
|
+
state.filteredItems = results;
|
|
170
|
+
state.selectedIndex = 0;
|
|
171
|
+
state.currentPage = 0;
|
|
172
|
+
}
|
|
173
|
+
// =============================================================================
|
|
174
|
+
// Rendering
|
|
175
|
+
// =============================================================================
|
|
176
|
+
function render(state, prevLineCount) {
|
|
177
|
+
const s = getStyles();
|
|
178
|
+
const lines = [];
|
|
179
|
+
const cols = terminal.getTerminalWidth();
|
|
180
|
+
const border = s.muted('─'.repeat(Math.max(1, cols - 1)));
|
|
181
|
+
// Clear previous content
|
|
182
|
+
terminal.clearLinesAbove(prevLineCount);
|
|
183
|
+
// Header
|
|
184
|
+
lines.push(border);
|
|
185
|
+
lines.push(' ' + s.primaryBold('BACKLOG') + s.muted(` (${String(state.items.length)} total)`));
|
|
186
|
+
lines.push('');
|
|
187
|
+
if (state.mode === 'view') {
|
|
188
|
+
// Search box - show cursor only when in search mode
|
|
189
|
+
const searchCursor = state.isSearching ? '█' : '';
|
|
190
|
+
const searchPrefix = state.isSearching ? s.primary(' Search: ') : s.muted(' Search: ');
|
|
191
|
+
lines.push(searchPrefix + state.searchQuery + searchCursor);
|
|
192
|
+
// Filter tabs
|
|
193
|
+
const tabAll = state.filterType === 'all' ? s.selected(' All ') : s.muted(' All ');
|
|
194
|
+
const tabActive = state.filterType === 'active' ? s.selected(' Active ') : s.muted(' Active ');
|
|
195
|
+
const tabFeature = state.filterType === 'feature' ? s.selected(' Feature ') : s.muted(' Feature ');
|
|
196
|
+
const tabBug = state.filterType === 'bug' ? s.selected(' Bug ') : s.muted(' Bug ');
|
|
197
|
+
const tabTech = state.filterType === 'tech-debt' ? s.selected(' Tech ') : s.muted(' Tech ');
|
|
198
|
+
const tabChore = state.filterType === 'chore' ? s.selected(' Chore ') : s.muted(' Chore ');
|
|
199
|
+
lines.push(` Filter: ${tabAll}${tabActive}${tabFeature}${tabBug}${tabTech}${tabChore} ${s.muted('(Tab)')}`);
|
|
200
|
+
lines.push('');
|
|
201
|
+
// Results info
|
|
202
|
+
const totalResults = state.filteredItems.length;
|
|
203
|
+
const totalPages = Math.ceil(totalResults / PAGE_SIZE);
|
|
204
|
+
const startIndex = state.currentPage * PAGE_SIZE;
|
|
205
|
+
const endIndex = Math.min(startIndex + PAGE_SIZE, totalResults);
|
|
206
|
+
const pageItems = state.filteredItems.slice(startIndex, endIndex);
|
|
207
|
+
if (totalResults === 0) {
|
|
208
|
+
lines.push(s.muted(' No items found'));
|
|
209
|
+
if (state.searchQuery || state.filterType !== 'all') {
|
|
210
|
+
lines.push(s.muted(' Try clearing search or changing filter'));
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
lines.push(s.muted(' Use [a] to add a new item or /design to create initial backlog'));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
lines.push(s.muted(` Showing ${String(startIndex + 1)}-${String(endIndex)} of ${String(totalResults)}:`));
|
|
218
|
+
lines.push('');
|
|
219
|
+
// Table header
|
|
220
|
+
const idW = 10;
|
|
221
|
+
const typeW = 10;
|
|
222
|
+
const statusW = 4;
|
|
223
|
+
const priW = 8;
|
|
224
|
+
const titleW = Math.max(20, cols - idW - typeW - statusW - priW - 10);
|
|
225
|
+
lines.push(s.muted(` ${'ID'.padEnd(idW)}${'Type'.padEnd(typeW)}${'St'.padEnd(statusW)}${'Pri'.padEnd(priW)}${'Title'.slice(0, titleW)}`));
|
|
226
|
+
// Items
|
|
227
|
+
for (let i = 0; i < pageItems.length; i++) {
|
|
228
|
+
const item = pageItems[i];
|
|
229
|
+
const isSelected = i === state.selectedIndex;
|
|
230
|
+
const prefix = isSelected ? '❯' : ' ';
|
|
231
|
+
const row = `${prefix}${item.id.padEnd(idW)}${item.type.padEnd(typeW)}${item.status.padEnd(statusW)}${item.priority.padEnd(priW)}${item.title.slice(0, titleW - 2)}`;
|
|
232
|
+
if (isSelected) {
|
|
233
|
+
lines.push(s.primary(' ' + row));
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
lines.push(s.muted(' ' + row));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Selected item details (truncate to prevent line wrapping)
|
|
240
|
+
if (pageItems.length > 0 && state.selectedIndex < pageItems.length) {
|
|
241
|
+
lines.push('');
|
|
242
|
+
const selected = pageItems[state.selectedIndex];
|
|
243
|
+
const descPrefix = ' Desc: ';
|
|
244
|
+
const maxDescLen = cols - descPrefix.length - 2;
|
|
245
|
+
const truncatedDesc = selected.description.length > maxDescLen
|
|
246
|
+
? selected.description.slice(0, maxDescLen - 3) + '...'
|
|
247
|
+
: selected.description;
|
|
248
|
+
lines.push(s.muted(descPrefix) + truncatedDesc);
|
|
249
|
+
if (selected.commit) {
|
|
250
|
+
lines.push(s.muted(' Commit: ') + selected.commit);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
lines.push('');
|
|
255
|
+
// Pagination
|
|
256
|
+
if (totalPages > 1) {
|
|
257
|
+
const pageInfo = `Page ${String(state.currentPage + 1)} of ${String(totalPages)}`;
|
|
258
|
+
const prevHint = state.currentPage > 0 ? '← ' : ' ';
|
|
259
|
+
const nextHint = state.currentPage < totalPages - 1 ? ' →' : '';
|
|
260
|
+
lines.push(s.muted(` ${prevHint}${pageInfo}${nextHint}`));
|
|
261
|
+
}
|
|
262
|
+
lines.push(border);
|
|
263
|
+
if (state.isSearching) {
|
|
264
|
+
lines.push(s.muted(' Type to search · Enter/Esc Exit search · Backspace Delete'));
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
lines.push(s.muted(' / Search · ↑↓ Navigate · ←→ Pages · Tab Filter · Enter Detail · Space Toggle · a Add · Esc Close'));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (state.mode === 'detail') {
|
|
271
|
+
// Detail view
|
|
272
|
+
const item = state.detailItem;
|
|
273
|
+
if (item) {
|
|
274
|
+
lines.push(chalk.bold(` ${item.id}: ${item.title}`));
|
|
275
|
+
lines.push('');
|
|
276
|
+
// Editable fields with selection indicator
|
|
277
|
+
const typePrefix = state.detailField === 0 ? s.primary('❯ Type: ') : s.muted(' Type: ');
|
|
278
|
+
const typeValue = state.detailField === 0 ? s.primary(TYPE_LABELS[item.type]) : TYPE_LABELS[item.type];
|
|
279
|
+
lines.push(typePrefix + typeValue);
|
|
280
|
+
const statusPrefix = state.detailField === 1 ? s.primary('❯ Status: ') : s.muted(' Status: ');
|
|
281
|
+
const statusValue = item.status + ' ' + getStatusLabel(item.status);
|
|
282
|
+
lines.push(statusPrefix + (state.detailField === 1 ? s.primary(statusValue) : statusValue));
|
|
283
|
+
const priPrefix = state.detailField === 2 ? s.primary('❯ Priority: ') : s.muted(' Priority: ');
|
|
284
|
+
const priValue = state.detailField === 2 ? s.primary(PRIORITY_LABELS[item.priority]) : PRIORITY_LABELS[item.priority];
|
|
285
|
+
lines.push(priPrefix + priValue);
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push(s.muted(' Description:'));
|
|
288
|
+
// Word-wrap description to fit terminal width
|
|
289
|
+
const descLines = wrapText(item.description, cols - 4);
|
|
290
|
+
for (const line of descLines) {
|
|
291
|
+
lines.push(' ' + line);
|
|
292
|
+
}
|
|
293
|
+
if (item.commit) {
|
|
294
|
+
lines.push('');
|
|
295
|
+
lines.push(s.muted(' Commit: ') + item.commit);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
lines.push('');
|
|
299
|
+
lines.push(border);
|
|
300
|
+
lines.push(s.muted(' ↑↓ Select field · ←→ Change value · Esc Back'));
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Add mode rendering
|
|
304
|
+
const stepLabels = ['Type', 'Priority', 'Title', 'Description', 'Confirm'];
|
|
305
|
+
lines.push(s.muted(` Step ${String(state.addStep + 1)}/5: ${stepLabels[state.addStep]}`));
|
|
306
|
+
lines.push('');
|
|
307
|
+
switch (state.addStep) {
|
|
308
|
+
case 0: // Type
|
|
309
|
+
lines.push(chalk.bold(' What type of item?'));
|
|
310
|
+
lines.push('');
|
|
311
|
+
for (let i = 0; i < TYPE_OPTIONS.length; i++) {
|
|
312
|
+
const isSelected = state.selectedIndex === i;
|
|
313
|
+
const prefix = isSelected ? ' ❯ ' : ' ';
|
|
314
|
+
const label = `${String(i + 1)}. ${TYPE_LABELS[TYPE_OPTIONS[i]]}`;
|
|
315
|
+
lines.push(isSelected ? s.primary(prefix + label) : s.muted(prefix + label));
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
case 1: // Priority
|
|
319
|
+
lines.push(chalk.bold(' Priority level?'));
|
|
320
|
+
lines.push('');
|
|
321
|
+
for (let i = 0; i < PRIORITY_OPTIONS.length; i++) {
|
|
322
|
+
const isSelected = state.selectedIndex === i;
|
|
323
|
+
const prefix = isSelected ? ' ❯ ' : ' ';
|
|
324
|
+
const label = `${String(i + 1)}. ${PRIORITY_LABELS[PRIORITY_OPTIONS[i]]}`;
|
|
325
|
+
lines.push(isSelected ? s.primary(prefix + label) : s.muted(prefix + label));
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
case 2: // Title
|
|
329
|
+
lines.push(chalk.bold(' Title (short description)'));
|
|
330
|
+
lines.push('');
|
|
331
|
+
lines.push(` > ${state.inputBuffer}█`);
|
|
332
|
+
lines.push('');
|
|
333
|
+
if (state.error) {
|
|
334
|
+
lines.push(s.error(` ${state.error}`));
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
lines.push(s.muted(' Keep it concise (2-100 characters)'));
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
case 3: // Description
|
|
341
|
+
lines.push(chalk.bold(' Description (details)'));
|
|
342
|
+
lines.push('');
|
|
343
|
+
lines.push(` > ${state.inputBuffer}█`);
|
|
344
|
+
lines.push('');
|
|
345
|
+
if (state.error) {
|
|
346
|
+
lines.push(s.error(` ${state.error}`));
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
lines.push(s.muted(' Explain the requirement or issue'));
|
|
350
|
+
}
|
|
351
|
+
break;
|
|
352
|
+
case 4: // Confirm
|
|
353
|
+
lines.push(chalk.bold(' Confirm new item:'));
|
|
354
|
+
lines.push('');
|
|
355
|
+
lines.push(s.muted(' Type: ') + (state.newItem.type ? TYPE_LABELS[state.newItem.type] : 'N/A'));
|
|
356
|
+
lines.push(s.muted(' Priority: ') + (state.newItem.priority ? PRIORITY_LABELS[state.newItem.priority] : 'N/A'));
|
|
357
|
+
lines.push(s.muted(' Title: ') + state.newItem.title);
|
|
358
|
+
lines.push(s.muted(' Description: ') + state.newItem.description);
|
|
359
|
+
lines.push('');
|
|
360
|
+
for (let i = 0; i < 2; i++) {
|
|
361
|
+
const isSelected = state.selectedIndex === i;
|
|
362
|
+
const prefix = isSelected ? ' ❯ ' : ' ';
|
|
363
|
+
const label = i === 0 ? '1. Create item' : '2. Cancel';
|
|
364
|
+
lines.push(isSelected ? s.primary(prefix + label) : s.muted(prefix + label));
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
lines.push('');
|
|
369
|
+
lines.push(border);
|
|
370
|
+
if (state.addStep === 2 || state.addStep === 3) {
|
|
371
|
+
lines.push(s.muted(' Enter Confirm · Esc Go back'));
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
lines.push(s.muted(' ↑↓ Navigate · Enter Select · Esc Go back'));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Write new content
|
|
378
|
+
terminal.write(lines.join('\n'));
|
|
379
|
+
return lines.length;
|
|
380
|
+
}
|
|
381
|
+
// =============================================================================
|
|
382
|
+
// Helper Functions
|
|
383
|
+
// =============================================================================
|
|
384
|
+
function getStatusLabel(status) {
|
|
385
|
+
switch (status) {
|
|
386
|
+
case '📋': return 'Backlog';
|
|
387
|
+
case '🚧': return 'In Progress';
|
|
388
|
+
case '✅': return 'Completed';
|
|
389
|
+
default: return '';
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function wrapText(text, maxWidth) {
|
|
393
|
+
if (text.length <= maxWidth)
|
|
394
|
+
return [text];
|
|
395
|
+
const words = text.split(' ');
|
|
396
|
+
const lines = [];
|
|
397
|
+
let currentLine = '';
|
|
398
|
+
for (const word of words) {
|
|
399
|
+
if (currentLine.length === 0) {
|
|
400
|
+
currentLine = word;
|
|
401
|
+
}
|
|
402
|
+
else if (currentLine.length + 1 + word.length <= maxWidth) {
|
|
403
|
+
currentLine += ' ' + word;
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
lines.push(currentLine);
|
|
407
|
+
currentLine = word;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (currentLine.length > 0) {
|
|
411
|
+
lines.push(currentLine);
|
|
412
|
+
}
|
|
413
|
+
return lines;
|
|
414
|
+
}
|
|
415
|
+
function getMaxIndexForAddStep(step) {
|
|
416
|
+
switch (step) {
|
|
417
|
+
case 0: return TYPE_OPTIONS.length - 1;
|
|
418
|
+
case 1: return PRIORITY_OPTIONS.length - 1;
|
|
419
|
+
case 4: return 1;
|
|
420
|
+
default: return 0;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
// =============================================================================
|
|
424
|
+
// Main Export
|
|
425
|
+
// =============================================================================
|
|
426
|
+
export async function showBacklogOverlay() {
|
|
427
|
+
const backlogPath = findBacklogPath();
|
|
428
|
+
let items = [];
|
|
429
|
+
if (backlogPath && fs.existsSync(backlogPath)) {
|
|
430
|
+
const content = fs.readFileSync(backlogPath, 'utf-8');
|
|
431
|
+
items = parseBacklogItems(content);
|
|
432
|
+
}
|
|
433
|
+
const state = {
|
|
434
|
+
mode: 'view',
|
|
435
|
+
items,
|
|
436
|
+
filteredItems: [...items],
|
|
437
|
+
selectedIndex: 0,
|
|
438
|
+
backlogPath,
|
|
439
|
+
searchQuery: '',
|
|
440
|
+
filterType: 'all',
|
|
441
|
+
isSearching: false,
|
|
442
|
+
currentPage: 0,
|
|
443
|
+
detailItem: null,
|
|
444
|
+
detailField: 0,
|
|
445
|
+
addStep: 0,
|
|
446
|
+
newItem: {
|
|
447
|
+
type: null,
|
|
448
|
+
priority: null,
|
|
449
|
+
title: '',
|
|
450
|
+
description: '',
|
|
451
|
+
},
|
|
452
|
+
inputBuffer: '',
|
|
453
|
+
error: null,
|
|
454
|
+
};
|
|
455
|
+
let lineCount = 0;
|
|
456
|
+
let modified = false;
|
|
457
|
+
terminal.writeLine('');
|
|
458
|
+
terminal.hideCursor();
|
|
459
|
+
const wasRawMode = process.stdin.isRaw;
|
|
460
|
+
terminal.enableRawMode();
|
|
461
|
+
lineCount = render(state, 0);
|
|
462
|
+
// Save changes to file
|
|
463
|
+
const saveChanges = () => {
|
|
464
|
+
if (state.backlogPath) {
|
|
465
|
+
const content = generateBacklogMarkdown(state.items);
|
|
466
|
+
const dir = path.dirname(state.backlogPath);
|
|
467
|
+
if (!fs.existsSync(dir)) {
|
|
468
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
469
|
+
}
|
|
470
|
+
fs.writeFileSync(state.backlogPath, content, 'utf-8');
|
|
471
|
+
modified = true;
|
|
472
|
+
// Update filtered items after save
|
|
473
|
+
updateFilteredItems(state);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
return new Promise((resolve) => {
|
|
477
|
+
const cleanup = () => {
|
|
478
|
+
terminal.clearLinesAbove(lineCount);
|
|
479
|
+
terminal.writeLine('');
|
|
480
|
+
terminal.showCursor();
|
|
481
|
+
if (!wasRawMode) {
|
|
482
|
+
terminal.disableRawMode();
|
|
483
|
+
}
|
|
484
|
+
process.stdin.removeListener('data', handleData);
|
|
485
|
+
};
|
|
486
|
+
const handleData = (data) => {
|
|
487
|
+
const isEscape = data.length === 1 && data[0] === 0x1b;
|
|
488
|
+
const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
|
|
489
|
+
const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
|
|
490
|
+
const isLeftArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x44;
|
|
491
|
+
const isRightArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x43;
|
|
492
|
+
const isCtrlC = data.length === 1 && data[0] === 0x03;
|
|
493
|
+
const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
|
|
494
|
+
const isBackspace = data.length === 1 && (data[0] === 0x7f || data[0] === 0x08);
|
|
495
|
+
const isSpace = data.length === 1 && data[0] === 0x20;
|
|
496
|
+
const isTab = data.length === 1 && data[0] === 0x09;
|
|
497
|
+
const char = data.length === 1 && data[0] >= 0x20 && data[0] < 0x7f ? String.fromCharCode(data[0]) : null;
|
|
498
|
+
if (isCtrlC) {
|
|
499
|
+
cleanup();
|
|
500
|
+
resolve({ modified });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
// ===== VIEW MODE =====
|
|
504
|
+
if (state.mode === 'view') {
|
|
505
|
+
const totalResults = state.filteredItems.length;
|
|
506
|
+
const totalPages = Math.ceil(totalResults / PAGE_SIZE);
|
|
507
|
+
const pageItems = state.filteredItems.slice(state.currentPage * PAGE_SIZE, (state.currentPage + 1) * PAGE_SIZE);
|
|
508
|
+
// ----- SEARCH MODE -----
|
|
509
|
+
if (state.isSearching) {
|
|
510
|
+
// Escape or Enter exits search mode
|
|
511
|
+
if (isEscape || isEnter) {
|
|
512
|
+
state.isSearching = false;
|
|
513
|
+
lineCount = render(state, lineCount);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
// Backspace deletes search char
|
|
517
|
+
if (isBackspace) {
|
|
518
|
+
state.searchQuery = state.searchQuery.slice(0, -1);
|
|
519
|
+
updateFilteredItems(state);
|
|
520
|
+
lineCount = render(state, lineCount);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
// All printable chars go to search
|
|
524
|
+
if (char) {
|
|
525
|
+
state.searchQuery += char;
|
|
526
|
+
updateFilteredItems(state);
|
|
527
|
+
lineCount = render(state, lineCount);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
// ----- NORMAL MODE (not searching) -----
|
|
533
|
+
// Escape closes overlay
|
|
534
|
+
if (isEscape) {
|
|
535
|
+
cleanup();
|
|
536
|
+
resolve({ modified });
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
// '/' enters search mode (vim-style)
|
|
540
|
+
if (char === '/') {
|
|
541
|
+
state.isSearching = true;
|
|
542
|
+
lineCount = render(state, lineCount);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
// Tab cycles filter type
|
|
546
|
+
if (isTab) {
|
|
547
|
+
const filters = ['all', 'active', 'feature', 'bug', 'tech-debt', 'chore'];
|
|
548
|
+
const currentIdx = filters.indexOf(state.filterType);
|
|
549
|
+
state.filterType = filters[(currentIdx + 1) % filters.length];
|
|
550
|
+
updateFilteredItems(state);
|
|
551
|
+
lineCount = render(state, lineCount);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
// Navigation
|
|
555
|
+
if (isUpArrow && state.selectedIndex > 0) {
|
|
556
|
+
state.selectedIndex--;
|
|
557
|
+
lineCount = render(state, lineCount);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (isDownArrow && state.selectedIndex < pageItems.length - 1) {
|
|
561
|
+
state.selectedIndex++;
|
|
562
|
+
lineCount = render(state, lineCount);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
// Page navigation
|
|
566
|
+
if (isLeftArrow && state.currentPage > 0) {
|
|
567
|
+
state.currentPage--;
|
|
568
|
+
state.selectedIndex = 0;
|
|
569
|
+
lineCount = render(state, lineCount);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (isRightArrow && state.currentPage < totalPages - 1) {
|
|
573
|
+
state.currentPage++;
|
|
574
|
+
state.selectedIndex = 0;
|
|
575
|
+
lineCount = render(state, lineCount);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
// Space - toggle status
|
|
579
|
+
if (isSpace && pageItems.length > 0) {
|
|
580
|
+
const item = pageItems[state.selectedIndex];
|
|
581
|
+
// Find actual item in state.items
|
|
582
|
+
const actualItem = state.items.find(i => i.id === item.id);
|
|
583
|
+
if (actualItem) {
|
|
584
|
+
const statusIdx = STATUS_OPTIONS.indexOf(actualItem.status);
|
|
585
|
+
actualItem.status = STATUS_OPTIONS[(statusIdx + 1) % STATUS_OPTIONS.length];
|
|
586
|
+
saveChanges();
|
|
587
|
+
}
|
|
588
|
+
lineCount = render(state, lineCount);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
// Enter - open detail view
|
|
592
|
+
if (isEnter && pageItems.length > 0) {
|
|
593
|
+
state.detailItem = pageItems[state.selectedIndex];
|
|
594
|
+
state.mode = 'detail';
|
|
595
|
+
lineCount = render(state, lineCount);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
// 'a' - add mode
|
|
599
|
+
if (char === 'a' || char === 'A') {
|
|
600
|
+
state.mode = 'add';
|
|
601
|
+
state.addStep = 0;
|
|
602
|
+
state.selectedIndex = 0;
|
|
603
|
+
state.newItem = { type: null, priority: null, title: '', description: '' };
|
|
604
|
+
state.inputBuffer = '';
|
|
605
|
+
state.error = null;
|
|
606
|
+
lineCount = render(state, lineCount);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
// ===== DETAIL MODE =====
|
|
612
|
+
if (state.mode === 'detail') {
|
|
613
|
+
// Escape - back to view
|
|
614
|
+
if (isEscape) {
|
|
615
|
+
state.mode = 'view';
|
|
616
|
+
state.detailItem = null;
|
|
617
|
+
state.detailField = 0;
|
|
618
|
+
lineCount = render(state, lineCount);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
// Up/Down - navigate fields
|
|
622
|
+
if (isUpArrow && state.detailField > 0) {
|
|
623
|
+
state.detailField = (state.detailField - 1);
|
|
624
|
+
lineCount = render(state, lineCount);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (isDownArrow && state.detailField < 2) {
|
|
628
|
+
state.detailField = (state.detailField + 1);
|
|
629
|
+
lineCount = render(state, lineCount);
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
// Left/Right - change value of selected field
|
|
633
|
+
if ((isLeftArrow || isRightArrow) && state.detailItem) {
|
|
634
|
+
const detailId = state.detailItem.id;
|
|
635
|
+
const actualItem = state.items.find(i => i.id === detailId);
|
|
636
|
+
if (actualItem) {
|
|
637
|
+
const direction = isRightArrow ? 1 : -1;
|
|
638
|
+
if (state.detailField === 0) {
|
|
639
|
+
// Type
|
|
640
|
+
const currentIdx = TYPE_OPTIONS.indexOf(actualItem.type);
|
|
641
|
+
const newIdx = (currentIdx + direction + TYPE_OPTIONS.length) % TYPE_OPTIONS.length;
|
|
642
|
+
actualItem.type = TYPE_OPTIONS[newIdx];
|
|
643
|
+
}
|
|
644
|
+
else if (state.detailField === 1) {
|
|
645
|
+
// Status
|
|
646
|
+
const currentIdx = STATUS_OPTIONS.indexOf(actualItem.status);
|
|
647
|
+
const newIdx = (currentIdx + direction + STATUS_OPTIONS.length) % STATUS_OPTIONS.length;
|
|
648
|
+
actualItem.status = STATUS_OPTIONS[newIdx];
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
// Priority
|
|
652
|
+
const currentIdx = PRIORITY_OPTIONS.indexOf(actualItem.priority);
|
|
653
|
+
const newIdx = (currentIdx + direction + PRIORITY_OPTIONS.length) % PRIORITY_OPTIONS.length;
|
|
654
|
+
actualItem.priority = PRIORITY_OPTIONS[newIdx];
|
|
655
|
+
}
|
|
656
|
+
state.detailItem = actualItem;
|
|
657
|
+
saveChanges();
|
|
658
|
+
}
|
|
659
|
+
lineCount = render(state, lineCount);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
// ===== ADD MODE =====
|
|
665
|
+
{
|
|
666
|
+
if (state.addStep === 2 || state.addStep === 3) {
|
|
667
|
+
// Text input steps
|
|
668
|
+
if (isEscape) {
|
|
669
|
+
if (state.addStep === 2) {
|
|
670
|
+
state.addStep = 1;
|
|
671
|
+
state.selectedIndex = 0;
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
state.addStep = 2;
|
|
675
|
+
state.inputBuffer = state.newItem.title;
|
|
676
|
+
}
|
|
677
|
+
state.error = null;
|
|
678
|
+
}
|
|
679
|
+
else if (isEnter) {
|
|
680
|
+
const text = state.inputBuffer.trim();
|
|
681
|
+
if (state.addStep === 2) {
|
|
682
|
+
if (text.length < 2) {
|
|
683
|
+
state.error = 'Title too short (min 2 characters)';
|
|
684
|
+
}
|
|
685
|
+
else if (text.length > 100) {
|
|
686
|
+
state.error = 'Title too long (max 100 characters)';
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
state.newItem.title = text;
|
|
690
|
+
state.addStep = 3;
|
|
691
|
+
state.inputBuffer = '';
|
|
692
|
+
state.error = null;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
if (text.length < 5) {
|
|
697
|
+
state.error = 'Description too short (min 5 characters)';
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
state.newItem.description = text;
|
|
701
|
+
state.addStep = 4;
|
|
702
|
+
state.selectedIndex = 0;
|
|
703
|
+
state.error = null;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
else if (isBackspace) {
|
|
708
|
+
state.inputBuffer = state.inputBuffer.slice(0, -1);
|
|
709
|
+
}
|
|
710
|
+
else if (char) {
|
|
711
|
+
state.inputBuffer += char;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
// Selection steps
|
|
716
|
+
if (isEscape) {
|
|
717
|
+
if (state.addStep === 0) {
|
|
718
|
+
state.mode = 'view';
|
|
719
|
+
state.selectedIndex = 0;
|
|
720
|
+
}
|
|
721
|
+
else if (state.addStep === 4) {
|
|
722
|
+
state.addStep = 3;
|
|
723
|
+
state.inputBuffer = state.newItem.description;
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
state.addStep = (state.addStep - 1);
|
|
727
|
+
state.selectedIndex = 0;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
else if (isUpArrow) {
|
|
731
|
+
state.selectedIndex = Math.max(0, state.selectedIndex - 1);
|
|
732
|
+
}
|
|
733
|
+
else if (isDownArrow) {
|
|
734
|
+
const maxIdx = getMaxIndexForAddStep(state.addStep);
|
|
735
|
+
state.selectedIndex = Math.min(maxIdx, state.selectedIndex + 1);
|
|
736
|
+
}
|
|
737
|
+
else if (isEnter) {
|
|
738
|
+
switch (state.addStep) {
|
|
739
|
+
case 0:
|
|
740
|
+
state.newItem.type = TYPE_OPTIONS[state.selectedIndex];
|
|
741
|
+
state.addStep = 1;
|
|
742
|
+
state.selectedIndex = 0;
|
|
743
|
+
break;
|
|
744
|
+
case 1:
|
|
745
|
+
state.newItem.priority = PRIORITY_OPTIONS[state.selectedIndex];
|
|
746
|
+
state.addStep = 2;
|
|
747
|
+
state.inputBuffer = '';
|
|
748
|
+
break;
|
|
749
|
+
case 4:
|
|
750
|
+
if (state.newItem.type && state.newItem.priority) {
|
|
751
|
+
if (state.selectedIndex === 0) {
|
|
752
|
+
// Create item
|
|
753
|
+
const id = generateId(state.newItem.type, state.items);
|
|
754
|
+
const newItem = {
|
|
755
|
+
id,
|
|
756
|
+
type: state.newItem.type,
|
|
757
|
+
status: '📋',
|
|
758
|
+
priority: state.newItem.priority,
|
|
759
|
+
title: state.newItem.title,
|
|
760
|
+
description: state.newItem.description,
|
|
761
|
+
};
|
|
762
|
+
state.items.push(newItem);
|
|
763
|
+
updateFilteredItems(state);
|
|
764
|
+
saveChanges();
|
|
765
|
+
}
|
|
766
|
+
state.mode = 'view';
|
|
767
|
+
state.selectedIndex = Math.min(state.selectedIndex, Math.max(0, state.filteredItems.length - 1));
|
|
768
|
+
}
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
else if (char && char >= '1' && char <= '9') {
|
|
773
|
+
const numIdx = parseInt(char, 10) - 1;
|
|
774
|
+
const maxIdx = getMaxIndexForAddStep(state.addStep);
|
|
775
|
+
if (numIdx <= maxIdx) {
|
|
776
|
+
state.selectedIndex = numIdx;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
lineCount = render(state, lineCount);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
process.stdin.on('data', handleData);
|
|
785
|
+
});
|
|
786
|
+
}
|