@compilr-dev/cli 0.4.0 → 0.5.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 +30 -12
- package/dist/agent.d.ts +74 -1
- package/dist/agent.js +259 -76
- package/dist/anchors/index.d.ts +9 -0
- package/dist/anchors/index.js +9 -0
- package/dist/anchors/project-anchors.d.ts +79 -0
- package/dist/anchors/project-anchors.js +202 -0
- package/dist/commands/handler-types.d.ts +68 -0
- package/dist/commands/handler-types.js +8 -0
- package/dist/commands/handlers/agent-commands.d.ts +13 -0
- package/dist/commands/handlers/agent-commands.js +305 -0
- package/dist/commands/handlers/design-commands.d.ts +15 -0
- package/dist/commands/handlers/design-commands.js +334 -0
- package/dist/commands/handlers/index.d.ts +20 -0
- package/dist/commands/handlers/index.js +43 -0
- package/dist/commands/handlers/overlay-commands.d.ts +21 -0
- package/dist/commands/handlers/overlay-commands.js +287 -0
- package/dist/commands/handlers/project-commands.d.ts +11 -0
- package/dist/commands/handlers/project-commands.js +167 -0
- package/dist/commands/handlers/simple-commands.d.ts +19 -0
- package/dist/commands/handlers/simple-commands.js +144 -0
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/registry.d.ts +50 -0
- package/dist/commands/registry.js +75 -0
- package/dist/commands-v2/handlers/context.d.ts +13 -0
- package/dist/commands-v2/handlers/context.js +348 -0
- package/dist/commands-v2/handlers/core.d.ts +13 -0
- package/dist/commands-v2/handlers/core.js +165 -0
- package/dist/commands-v2/handlers/debug.d.ts +11 -0
- package/dist/commands-v2/handlers/debug.js +159 -0
- package/dist/commands-v2/handlers/index.d.ts +12 -0
- package/dist/commands-v2/handlers/index.js +24 -0
- package/dist/commands-v2/handlers/project.d.ts +22 -0
- package/dist/commands-v2/handlers/project.js +814 -0
- package/dist/commands-v2/handlers/settings.d.ts +15 -0
- package/dist/commands-v2/handlers/settings.js +235 -0
- package/dist/commands-v2/index.d.ts +13 -0
- package/dist/commands-v2/index.js +15 -0
- package/dist/commands-v2/registry.d.ts +37 -0
- package/dist/commands-v2/registry.js +80 -0
- package/dist/commands-v2/types.d.ts +75 -0
- package/dist/commands-v2/types.js +7 -0
- package/dist/commands.js +110 -7
- package/dist/index.js +288 -29
- package/dist/input-handlers/index.d.ts +7 -0
- package/dist/input-handlers/index.js +7 -0
- package/dist/input-handlers/memory-handler.d.ts +26 -0
- package/dist/input-handlers/memory-handler.js +68 -0
- package/dist/repl-helpers.d.ts +63 -0
- package/dist/repl-helpers.js +318 -0
- package/dist/repl-v2.d.ts +155 -0
- package/dist/repl-v2.js +774 -0
- package/dist/repl.d.ts +32 -4
- package/dist/repl.js +250 -977
- package/dist/settings/index.d.ts +23 -0
- package/dist/settings/index.js +48 -0
- package/dist/settings/paths.d.ts +110 -0
- package/dist/settings/paths.js +264 -0
- package/dist/templates/compilr-md.js +7 -4
- package/dist/templates/index.js +3 -4
- package/dist/themes/colors.js +3 -1
- package/dist/themes/registry.d.ts +5 -36
- package/dist/themes/registry.js +11 -95
- package/dist/themes/types.d.ts +3 -38
- package/dist/themes/types.js +2 -2
- package/dist/tools/anchor-tools.d.ts +31 -0
- package/dist/tools/anchor-tools.js +255 -0
- package/dist/tools/backlog-wrappers.d.ts +54 -0
- package/dist/tools/backlog-wrappers.js +338 -0
- package/dist/tools/backlog.js +1 -1
- package/dist/tools/db-tools.d.ts +65 -0
- package/dist/tools/db-tools.js +19 -0
- package/dist/tools/document-db.d.ts +43 -0
- package/dist/tools/document-db.js +220 -0
- package/dist/tools/project-db.d.ts +102 -0
- package/dist/tools/project-db.js +370 -0
- package/dist/tools/workitem-db.d.ts +103 -0
- package/dist/tools/workitem-db.js +549 -0
- package/dist/tools.js +13 -3
- package/dist/ui/agents-overlay-v2.d.ts +43 -0
- package/dist/ui/agents-overlay-v2.js +809 -0
- package/dist/ui/agents-overlay.d.ts +5 -5
- package/dist/ui/agents-overlay.js +782 -420
- package/dist/ui/anchors-overlay.d.ts +12 -0
- package/dist/ui/anchors-overlay.js +775 -0
- package/dist/ui/arch-type-overlay.d.ts +1 -6
- package/dist/ui/arch-type-overlay.js +175 -203
- package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
- package/dist/ui/ask-user-overlay-v2.js +555 -0
- package/dist/ui/ask-user-overlay.d.ts +2 -2
- package/dist/ui/ask-user-overlay.js +443 -535
- package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
- package/dist/ui/ask-user-simple-overlay.js +182 -209
- package/dist/ui/backlog-overlay.d.ts +16 -1
- package/dist/ui/backlog-overlay.js +525 -659
- package/dist/ui/base/index.d.ts +26 -0
- package/dist/ui/base/index.js +33 -0
- package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
- package/dist/ui/base/inline-overlay-utils.js +320 -0
- package/dist/ui/base/inline-overlay.d.ts +159 -0
- package/dist/ui/base/inline-overlay.js +257 -0
- package/dist/ui/base/key-utils.d.ts +15 -0
- package/dist/ui/base/key-utils.js +30 -0
- package/dist/ui/base/overlay-base-v2.d.ts +193 -0
- package/dist/ui/base/overlay-base-v2.js +246 -0
- package/dist/ui/base/overlay-base.d.ts +156 -0
- package/dist/ui/base/overlay-base.js +238 -0
- package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
- package/dist/ui/base/overlay-lifecycle.js +159 -0
- package/dist/ui/base/overlay-types.d.ts +185 -0
- package/dist/ui/base/overlay-types.js +7 -0
- package/dist/ui/base/render-utils.d.ts +8 -0
- package/dist/ui/base/render-utils.js +11 -0
- package/dist/ui/base/screen-stack.d.ts +148 -0
- package/dist/ui/base/screen-stack.js +184 -0
- package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
- package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
- package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
- package/dist/ui/base/tabbed-list-overlay.js +369 -0
- package/dist/ui/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/commands-overlay-v2.js +441 -0
- package/dist/ui/commands-overlay.d.ts +7 -2
- package/dist/ui/commands-overlay.js +384 -355
- package/dist/ui/config-overlay.d.ts +5 -4
- package/dist/ui/config-overlay.js +243 -513
- package/dist/ui/conversation.d.ts +75 -4
- package/dist/ui/conversation.js +374 -161
- package/dist/ui/docs-overlay.d.ts +17 -0
- package/dist/ui/docs-overlay.js +303 -0
- package/dist/ui/ephemeral.d.ts +1 -1
- package/dist/ui/ephemeral.js +1 -1
- package/dist/ui/features/index.d.ts +34 -0
- package/dist/ui/features/index.js +34 -0
- package/dist/ui/features/input-feature.d.ts +85 -0
- package/dist/ui/features/input-feature.js +238 -0
- package/dist/ui/features/list-feature.d.ts +155 -0
- package/dist/ui/features/list-feature.js +244 -0
- package/dist/ui/features/pagination-feature.d.ts +154 -0
- package/dist/ui/features/pagination-feature.js +238 -0
- package/dist/ui/features/search-feature.d.ts +148 -0
- package/dist/ui/features/search-feature.js +185 -0
- package/dist/ui/features/tab-feature.d.ts +194 -0
- package/dist/ui/features/tab-feature.js +307 -0
- package/dist/ui/footer-v2.d.ts +222 -0
- package/dist/ui/footer-v2.js +1349 -0
- package/dist/ui/footer.d.ts +107 -0
- package/dist/ui/footer.js +359 -67
- package/dist/ui/guardrail-overlay.d.ts +29 -0
- package/dist/ui/guardrail-overlay.js +145 -0
- package/dist/ui/help-overlay-v2.d.ts +34 -0
- package/dist/ui/help-overlay-v2.js +309 -0
- package/dist/ui/help-overlay.d.ts +16 -0
- package/dist/ui/help-overlay.js +316 -0
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -3
- package/dist/ui/init-overlay-v2.d.ts +34 -0
- package/dist/ui/init-overlay-v2.js +600 -0
- package/dist/ui/init-overlay.d.ts +12 -2
- package/dist/ui/init-overlay.js +349 -270
- package/dist/ui/input-prompt-v2.d.ts +1 -0
- package/dist/ui/input-prompt-v2.js +14 -6
- package/dist/ui/input-prompt.d.ts +116 -33
- package/dist/ui/input-prompt.js +536 -337
- package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay-v2.js +114 -0
- package/dist/ui/iteration-limit-overlay.d.ts +2 -2
- package/dist/ui/iteration-limit-overlay.js +92 -128
- package/dist/ui/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/keys-overlay-v2.js +248 -0
- package/dist/ui/keys-overlay.d.ts +1 -0
- package/dist/ui/keys-overlay.js +203 -141
- package/dist/ui/line-utils.d.ts +88 -0
- package/dist/ui/line-utils.js +150 -0
- package/dist/ui/live-region.d.ts +161 -0
- package/dist/ui/live-region.js +387 -0
- package/dist/ui/mascot/expressions.d.ts +32 -0
- package/dist/ui/mascot/expressions.js +213 -0
- package/dist/ui/mascot/index.d.ts +8 -0
- package/dist/ui/mascot/index.js +8 -0
- package/dist/ui/mascot/renderer.d.ts +19 -0
- package/dist/ui/mascot/renderer.js +97 -0
- package/dist/ui/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/mascot-overlay-v2.js +138 -0
- package/dist/ui/mascot-overlay.d.ts +21 -0
- package/dist/ui/mascot-overlay.js +146 -0
- package/dist/ui/model-overlay-v2.d.ts +49 -0
- package/dist/ui/model-overlay-v2.js +118 -0
- package/dist/ui/model-overlay.d.ts +27 -0
- package/dist/ui/model-overlay.js +221 -0
- package/dist/ui/model-warning-overlay.js +3 -5
- package/dist/ui/new-overlay.d.ts +34 -0
- package/dist/ui/new-overlay.js +604 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
- package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
- package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
- package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
- package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
- package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
- package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
- package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
- package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
- package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
- package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
- package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
- package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
- package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
- package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
- package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
- package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
- package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
- package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
- package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
- package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
- package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
- package/dist/ui/overlay/index.d.ts +33 -0
- package/dist/ui/overlay/index.js +35 -0
- package/dist/ui/overlay/key-utils.d.ts +6 -0
- package/dist/ui/overlay/key-utils.js +6 -0
- package/dist/ui/overlay/overlay-types.d.ts +128 -0
- package/dist/ui/overlay/overlay-types.js +22 -0
- package/dist/ui/overlay/types.d.ts +135 -0
- package/dist/ui/overlay/types.js +22 -0
- package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
- package/dist/ui/overlays/help-overlay-v2.js +198 -0
- package/dist/ui/overlays/index.d.ts +11 -0
- package/dist/ui/overlays/index.js +11 -0
- package/dist/ui/overlays.d.ts +0 -4
- package/dist/ui/overlays.js +0 -444
- package/dist/ui/permission-overlay-v2.d.ts +36 -0
- package/dist/ui/permission-overlay-v2.js +380 -0
- package/dist/ui/permission-overlay.d.ts +1 -1
- package/dist/ui/permission-overlay.js +186 -298
- package/dist/ui/projects-overlay.d.ts +19 -0
- package/dist/ui/projects-overlay.js +484 -0
- package/dist/ui/providers/types.d.ts +178 -0
- package/dist/ui/providers/types.js +9 -0
- package/dist/ui/render-modes.d.ts +36 -0
- package/dist/ui/render-modes.js +44 -0
- package/dist/ui/startup-menu.d.ts +36 -0
- package/dist/ui/startup-menu.js +236 -0
- package/dist/ui/subagent-renderer.d.ts +117 -0
- package/dist/ui/subagent-renderer.js +334 -0
- package/dist/ui/terminal-codes.d.ts +94 -0
- package/dist/ui/terminal-codes.js +124 -0
- package/dist/ui/terminal-renderer.d.ts +221 -0
- package/dist/ui/terminal-renderer.js +751 -0
- package/dist/ui/terminal-ui.d.ts +463 -0
- package/dist/ui/terminal-ui.js +2296 -0
- package/dist/ui/terminal.d.ts +20 -0
- package/dist/ui/terminal.js +72 -0
- package/dist/ui/theme-overlay-v2.d.ts +42 -0
- package/dist/ui/theme-overlay-v2.js +135 -0
- package/dist/ui/theme-overlay.d.ts +24 -0
- package/dist/ui/theme-overlay.js +127 -0
- package/dist/ui/todo-zone.js +53 -25
- package/dist/ui/tool-formatters.d.ts +16 -0
- package/dist/ui/tool-formatters.js +516 -0
- package/dist/ui/tools-overlay-v2.d.ts +47 -0
- package/dist/ui/tools-overlay-v2.js +218 -0
- package/dist/ui/tools-overlay.d.ts +10 -2
- package/dist/ui/tools-overlay.js +172 -220
- package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
- package/dist/ui/tutorial-overlay-v2.js +1035 -0
- package/dist/ui/tutorial-overlay.d.ts +1 -0
- package/dist/ui/tutorial-overlay.js +400 -302
- package/dist/ui/workflow-overlay.d.ts +22 -0
- package/dist/ui/workflow-overlay.js +636 -0
- package/dist/utils/debug-log.d.ts +28 -0
- package/dist/utils/debug-log.js +57 -0
- package/dist/utils/model-tiers.js +1 -1
- package/dist/utils/path-safety.d.ts +56 -0
- package/dist/utils/path-safety.js +239 -0
- package/dist/workflow/guided-mode-injector.d.ts +42 -0
- package/dist/workflow/guided-mode-injector.js +191 -0
- package/dist/workflow/index.d.ts +8 -0
- package/dist/workflow/index.js +8 -0
- package/dist/workflow/step-criteria.d.ts +62 -0
- package/dist/workflow/step-criteria.js +150 -0
- package/dist/workflow/step-tracker.d.ts +92 -0
- package/dist/workflow/step-tracker.js +141 -0
- package/package.json +12 -5
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Backlog Overlay
|
|
3
3
|
*
|
|
4
4
|
* Modal overlay for viewing and managing the project backlog.
|
|
5
|
-
*
|
|
5
|
+
* Uses TabbedListOverlay for consistent list/tab/search behavior.
|
|
6
6
|
*
|
|
7
7
|
* Features:
|
|
8
8
|
* - Search by title/description
|
|
@@ -10,12 +10,13 @@
|
|
|
10
10
|
* - Pagination for large backlogs
|
|
11
11
|
* - Quick status toggle with Space
|
|
12
12
|
* - Add new items with wizard
|
|
13
|
+
* - Database-backed with project isolation
|
|
13
14
|
*/
|
|
14
15
|
import chalk from 'chalk';
|
|
15
|
-
import * as fs from 'fs';
|
|
16
|
-
import * as path from 'path';
|
|
17
16
|
import * as terminal from './terminal.js';
|
|
18
|
-
import {
|
|
17
|
+
import { TabbedListOverlay, BaseScreen, stay, popScreen, closeOverlay, isEscape, isCtrlC, isEnter, isSpace, isNavigateUp, isNavigateDown, isLeftArrow, isRightArrow, isVimLeft, isVimRight, isBackspace, extractPrintable, renderBorder, } from './base/index.js';
|
|
18
|
+
import { workItemRepository } from '../db/repositories/work-item-repository.js';
|
|
19
|
+
import { getActiveProject } from '../tools/project-db.js';
|
|
19
20
|
// =============================================================================
|
|
20
21
|
// Constants
|
|
21
22
|
// =============================================================================
|
|
@@ -35,752 +36,617 @@ const PRIORITY_LABELS = {
|
|
|
35
36
|
'medium': 'Medium',
|
|
36
37
|
'low': 'Low',
|
|
37
38
|
};
|
|
39
|
+
const TABS = [
|
|
40
|
+
{ id: 'all', label: 'All' },
|
|
41
|
+
{ id: 'active', label: 'Active' },
|
|
42
|
+
{ id: 'feature', label: 'Feature' },
|
|
43
|
+
{ id: 'bug', label: 'Bug' },
|
|
44
|
+
{ id: 'tech-debt', label: 'Tech' },
|
|
45
|
+
{ id: 'chore', label: 'Chore' },
|
|
46
|
+
];
|
|
38
47
|
// =============================================================================
|
|
39
|
-
//
|
|
48
|
+
// Status Mapping (Database ↔ Display)
|
|
40
49
|
// =============================================================================
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
}
|
|
50
|
+
/**
|
|
51
|
+
* Map database status to display emoji
|
|
52
|
+
*/
|
|
53
|
+
function dbStatusToEmoji(status) {
|
|
54
|
+
switch (status) {
|
|
55
|
+
case 'backlog': return '📋';
|
|
56
|
+
case 'in_progress': return '🚧';
|
|
57
|
+
case 'completed': return '✅';
|
|
58
|
+
case 'skipped': return '✅'; // Show skipped as completed for simplicity
|
|
59
|
+
default: return '📋';
|
|
110
60
|
}
|
|
111
|
-
return items;
|
|
112
61
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Map display emoji to database status
|
|
64
|
+
*/
|
|
65
|
+
function emojiToDbStatus(emoji) {
|
|
66
|
+
switch (emoji) {
|
|
67
|
+
case '📋': return 'backlog';
|
|
68
|
+
case '🚧': return 'in_progress';
|
|
69
|
+
case '✅': return 'completed';
|
|
70
|
+
default: return 'backlog';
|
|
122
71
|
}
|
|
123
|
-
content += `
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
*Last updated: ${date} by user*
|
|
127
|
-
`;
|
|
128
|
-
return content;
|
|
129
72
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Convert database WorkItem to display BacklogItem
|
|
75
|
+
*/
|
|
76
|
+
function workItemToBacklogItem(item) {
|
|
77
|
+
return {
|
|
78
|
+
id: item.itemId,
|
|
79
|
+
dbId: item.id,
|
|
80
|
+
type: item.type,
|
|
81
|
+
status: dbStatusToEmoji(item.status),
|
|
82
|
+
priority: item.priority,
|
|
83
|
+
title: item.title,
|
|
84
|
+
description: item.description ?? '',
|
|
85
|
+
commit: item.commitHash ?? undefined,
|
|
136
86
|
};
|
|
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
87
|
}
|
|
149
88
|
// =============================================================================
|
|
150
|
-
//
|
|
89
|
+
// Database Operations
|
|
151
90
|
// =============================================================================
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Load items from database for a project
|
|
93
|
+
*/
|
|
94
|
+
function loadItemsFromDb(projectId) {
|
|
95
|
+
const result = workItemRepository.query({
|
|
96
|
+
project_id: projectId,
|
|
97
|
+
limit: 1000, // Get all items
|
|
98
|
+
});
|
|
99
|
+
return result.items.map(workItemToBacklogItem);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Update an item's status in the database
|
|
103
|
+
*/
|
|
104
|
+
function updateItemStatus(dbId, status) {
|
|
105
|
+
workItemRepository.update(dbId, {
|
|
106
|
+
status: emojiToDbStatus(status),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Update an item's type in the database
|
|
111
|
+
*/
|
|
112
|
+
function updateItemType(dbId, type) {
|
|
113
|
+
workItemRepository.update(dbId, { type });
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Update an item's priority in the database
|
|
117
|
+
*/
|
|
118
|
+
function updateItemPriority(dbId, priority) {
|
|
119
|
+
workItemRepository.update(dbId, { priority });
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a new item in the database
|
|
123
|
+
*/
|
|
124
|
+
function createItem(projectId, type, priority, title, description) {
|
|
125
|
+
const item = workItemRepository.create({
|
|
126
|
+
project_id: projectId,
|
|
127
|
+
type,
|
|
128
|
+
priority,
|
|
129
|
+
title,
|
|
130
|
+
description,
|
|
131
|
+
});
|
|
132
|
+
return workItemToBacklogItem(item);
|
|
133
|
+
}
|
|
134
|
+
// =============================================================================
|
|
135
|
+
// Helper Functions
|
|
136
|
+
// =============================================================================
|
|
137
|
+
function getStatusLabel(status) {
|
|
138
|
+
switch (status) {
|
|
139
|
+
case '📋': return 'Backlog';
|
|
140
|
+
case '🚧': return 'In Progress';
|
|
141
|
+
case '✅': return 'Completed';
|
|
142
|
+
default: return '';
|
|
158
143
|
}
|
|
159
|
-
|
|
160
|
-
|
|
144
|
+
}
|
|
145
|
+
function wrapText(text, maxWidth) {
|
|
146
|
+
if (!text || text.length <= maxWidth)
|
|
147
|
+
return [text || ''];
|
|
148
|
+
const words = text.split(' ');
|
|
149
|
+
const lines = [];
|
|
150
|
+
let currentLine = '';
|
|
151
|
+
for (const word of words) {
|
|
152
|
+
if (currentLine.length === 0) {
|
|
153
|
+
currentLine = word;
|
|
154
|
+
}
|
|
155
|
+
else if (currentLine.length + 1 + word.length <= maxWidth) {
|
|
156
|
+
currentLine += ' ' + word;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
lines.push(currentLine);
|
|
160
|
+
currentLine = word;
|
|
161
|
+
}
|
|
161
162
|
}
|
|
162
|
-
|
|
163
|
-
|
|
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));
|
|
163
|
+
if (currentLine.length > 0) {
|
|
164
|
+
lines.push(currentLine);
|
|
168
165
|
}
|
|
169
|
-
|
|
170
|
-
state.selectedIndex = 0;
|
|
171
|
-
state.currentPage = 0;
|
|
166
|
+
return lines.length > 0 ? lines : [''];
|
|
172
167
|
}
|
|
173
168
|
// =============================================================================
|
|
174
|
-
//
|
|
169
|
+
// Detail Screen (In-Place Editing)
|
|
175
170
|
// =============================================================================
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
lines.push(` Filter: ${tabAll}${tabActive}${tabFeature}${tabBug}${tabTech}${tabChore} ${s.muted('(Tab)')}`);
|
|
171
|
+
/**
|
|
172
|
+
* Screen for editing item type, status, and priority.
|
|
173
|
+
* Renders in-place over the list.
|
|
174
|
+
*/
|
|
175
|
+
class DetailScreen extends BaseScreen {
|
|
176
|
+
item;
|
|
177
|
+
editState;
|
|
178
|
+
styles;
|
|
179
|
+
selectedField = 0; // 0=type, 1=status, 2=priority
|
|
180
|
+
constructor(item, editState, styles) {
|
|
181
|
+
super();
|
|
182
|
+
this.item = item;
|
|
183
|
+
this.editState = editState;
|
|
184
|
+
this.styles = styles;
|
|
185
|
+
}
|
|
186
|
+
render() {
|
|
187
|
+
const s = this.styles;
|
|
188
|
+
const cols = terminal.getTerminalWidth();
|
|
189
|
+
const border = renderBorder(cols, s);
|
|
190
|
+
const lines = [];
|
|
191
|
+
// Header
|
|
192
|
+
lines.push(border);
|
|
193
|
+
lines.push(` ${s.primaryBold('Edit Item')} ${s.muted(this.item.id)}`);
|
|
200
194
|
lines.push('');
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
195
|
+
// Title
|
|
196
|
+
lines.push(` ${chalk.bold(this.item.title)}`);
|
|
197
|
+
lines.push('');
|
|
198
|
+
// Editable fields with selection indicator
|
|
199
|
+
const typePrefix = this.selectedField === 0 ? s.primary('❯ Type: ') : s.muted(' Type: ');
|
|
200
|
+
const typeValue = this.selectedField === 0 ? s.primary(TYPE_LABELS[this.item.type]) : TYPE_LABELS[this.item.type];
|
|
201
|
+
lines.push(` ${typePrefix}${typeValue}`);
|
|
202
|
+
const statusLabel = this.item.status + ' ' + getStatusLabel(this.item.status);
|
|
203
|
+
const statusPrefix = this.selectedField === 1 ? s.primary('❯ Status: ') : s.muted(' Status: ');
|
|
204
|
+
const statusValue = this.selectedField === 1 ? s.primary(statusLabel) : statusLabel;
|
|
205
|
+
lines.push(` ${statusPrefix}${statusValue}`);
|
|
206
|
+
const priPrefix = this.selectedField === 2 ? s.primary('❯ Priority: ') : s.muted(' Priority: ');
|
|
207
|
+
const priValue = this.selectedField === 2 ? s.primary(PRIORITY_LABELS[this.item.priority]) : PRIORITY_LABELS[this.item.priority];
|
|
208
|
+
lines.push(` ${priPrefix}${priValue}`);
|
|
209
|
+
lines.push('');
|
|
210
|
+
// Description
|
|
211
|
+
lines.push(` ${s.muted('Description:')}`);
|
|
212
|
+
const descLines = wrapText(this.item.description, cols - 6);
|
|
213
|
+
for (const line of descLines) {
|
|
214
|
+
lines.push(` ${line}`);
|
|
215
215
|
}
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
// Commit info
|
|
217
|
+
if (this.item.commit) {
|
|
218
218
|
lines.push('');
|
|
219
|
-
|
|
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
|
-
}
|
|
219
|
+
lines.push(` ${s.muted('Commit:')} ${this.item.commit}`);
|
|
253
220
|
}
|
|
254
221
|
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
222
|
lines.push(border);
|
|
263
|
-
|
|
264
|
-
|
|
223
|
+
lines.push(` ${s.muted('↑↓/jk Select field · ←→/hl Change value · Esc Back')}`);
|
|
224
|
+
lines.push(border);
|
|
225
|
+
return lines;
|
|
226
|
+
}
|
|
227
|
+
handleKey(data) {
|
|
228
|
+
// Ctrl+C closes everything
|
|
229
|
+
if (isCtrlC(data)) {
|
|
230
|
+
return closeOverlay({ modified: this.editState.modified });
|
|
265
231
|
}
|
|
266
|
-
|
|
267
|
-
|
|
232
|
+
// Escape - back to list
|
|
233
|
+
if (isEscape(data)) {
|
|
234
|
+
return popScreen();
|
|
268
235
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
for (const line of descLines) {
|
|
291
|
-
lines.push(' ' + line);
|
|
236
|
+
// Up/Down or k/j - navigate fields
|
|
237
|
+
if (isNavigateUp(data) && this.selectedField > 0) {
|
|
238
|
+
this.selectedField = (this.selectedField - 1);
|
|
239
|
+
return stay();
|
|
240
|
+
}
|
|
241
|
+
if (isNavigateDown(data) && this.selectedField < 2) {
|
|
242
|
+
this.selectedField = (this.selectedField + 1);
|
|
243
|
+
return stay();
|
|
244
|
+
}
|
|
245
|
+
// Left/Right or h/l - change value of selected field
|
|
246
|
+
const isLeft = isLeftArrow(data) || isVimLeft(data);
|
|
247
|
+
const isRight = isRightArrow(data) || isVimRight(data);
|
|
248
|
+
if (isLeft || isRight) {
|
|
249
|
+
const direction = isRight ? 1 : -1;
|
|
250
|
+
if (this.selectedField === 0) {
|
|
251
|
+
// Type
|
|
252
|
+
const currentIdx = TYPE_OPTIONS.indexOf(this.item.type);
|
|
253
|
+
const newIdx = (currentIdx + direction + TYPE_OPTIONS.length) % TYPE_OPTIONS.length;
|
|
254
|
+
const newType = TYPE_OPTIONS[newIdx];
|
|
255
|
+
updateItemType(this.item.dbId, newType);
|
|
256
|
+
this.item.type = newType;
|
|
292
257
|
}
|
|
293
|
-
if (
|
|
294
|
-
|
|
295
|
-
|
|
258
|
+
else if (this.selectedField === 1) {
|
|
259
|
+
// Status
|
|
260
|
+
const currentIdx = STATUS_OPTIONS.indexOf(this.item.status);
|
|
261
|
+
const newIdx = (currentIdx + direction + STATUS_OPTIONS.length) % STATUS_OPTIONS.length;
|
|
262
|
+
const newStatus = STATUS_OPTIONS[newIdx];
|
|
263
|
+
updateItemStatus(this.item.dbId, newStatus);
|
|
264
|
+
this.item.status = newStatus;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
// Priority
|
|
268
|
+
const currentIdx = PRIORITY_OPTIONS.indexOf(this.item.priority);
|
|
269
|
+
const newIdx = (currentIdx + direction + PRIORITY_OPTIONS.length) % PRIORITY_OPTIONS.length;
|
|
270
|
+
const newPriority = PRIORITY_OPTIONS[newIdx];
|
|
271
|
+
updateItemPriority(this.item.dbId, newPriority);
|
|
272
|
+
this.item.priority = newPriority;
|
|
296
273
|
}
|
|
274
|
+
this.editState.modified = true;
|
|
275
|
+
this.editState.reloadItems();
|
|
276
|
+
return stay();
|
|
297
277
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
278
|
+
return stay(false);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// =============================================================================
|
|
282
|
+
// Add Screen (Multi-Step Wizard)
|
|
283
|
+
// =============================================================================
|
|
284
|
+
/**
|
|
285
|
+
* Screen for adding a new backlog item.
|
|
286
|
+
* 5 steps: type, priority, title, description, confirm
|
|
287
|
+
*/
|
|
288
|
+
class AddScreen extends BaseScreen {
|
|
289
|
+
editState;
|
|
290
|
+
styles;
|
|
291
|
+
step = 0;
|
|
292
|
+
selectedIndex = 0;
|
|
293
|
+
inputBuffer = '';
|
|
294
|
+
error = null;
|
|
295
|
+
newItem = { type: null, priority: null, title: '', description: '' };
|
|
296
|
+
constructor(editState, styles) {
|
|
297
|
+
super();
|
|
298
|
+
this.editState = editState;
|
|
299
|
+
this.styles = styles;
|
|
301
300
|
}
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
render() {
|
|
302
|
+
const s = this.styles;
|
|
303
|
+
const cols = terminal.getTerminalWidth();
|
|
304
|
+
const border = renderBorder(cols, s);
|
|
305
|
+
const lines = [];
|
|
306
|
+
// Header
|
|
307
|
+
lines.push(border);
|
|
308
|
+
lines.push(` ${s.primaryBold('Add Item')}`);
|
|
309
|
+
lines.push('');
|
|
304
310
|
const stepLabels = ['Type', 'Priority', 'Title', 'Description', 'Confirm'];
|
|
305
|
-
lines.push(s.muted(`
|
|
311
|
+
lines.push(` ${s.muted(`Step ${String(this.step + 1)}/5: ${stepLabels[this.step]}`)}`);
|
|
306
312
|
lines.push('');
|
|
307
|
-
switch (
|
|
313
|
+
switch (this.step) {
|
|
308
314
|
case 0: // Type
|
|
309
|
-
lines.push(chalk.bold('
|
|
315
|
+
lines.push(` ${chalk.bold('What type of item?')}`);
|
|
310
316
|
lines.push('');
|
|
311
317
|
for (let i = 0; i < TYPE_OPTIONS.length; i++) {
|
|
312
|
-
const isSelected =
|
|
313
|
-
const prefix = isSelected ? '
|
|
318
|
+
const isSelected = this.selectedIndex === i;
|
|
319
|
+
const prefix = isSelected ? s.primary('❯ ') : ' ';
|
|
314
320
|
const label = `${String(i + 1)}. ${TYPE_LABELS[TYPE_OPTIONS[i]]}`;
|
|
315
|
-
lines.push(isSelected ? s.primary(
|
|
321
|
+
lines.push(` ${prefix}${isSelected ? s.primary(label) : s.muted(label)}`);
|
|
316
322
|
}
|
|
317
323
|
break;
|
|
318
324
|
case 1: // Priority
|
|
319
|
-
lines.push(chalk.bold('
|
|
325
|
+
lines.push(` ${chalk.bold('Priority level?')}`);
|
|
320
326
|
lines.push('');
|
|
321
327
|
for (let i = 0; i < PRIORITY_OPTIONS.length; i++) {
|
|
322
|
-
const isSelected =
|
|
323
|
-
const prefix = isSelected ? '
|
|
328
|
+
const isSelected = this.selectedIndex === i;
|
|
329
|
+
const prefix = isSelected ? s.primary('❯ ') : ' ';
|
|
324
330
|
const label = `${String(i + 1)}. ${PRIORITY_LABELS[PRIORITY_OPTIONS[i]]}`;
|
|
325
|
-
lines.push(isSelected ? s.primary(
|
|
331
|
+
lines.push(` ${prefix}${isSelected ? s.primary(label) : s.muted(label)}`);
|
|
326
332
|
}
|
|
327
333
|
break;
|
|
328
334
|
case 2: // Title
|
|
329
|
-
lines.push(chalk.bold('
|
|
335
|
+
lines.push(` ${chalk.bold('Title (short description)')}`);
|
|
330
336
|
lines.push('');
|
|
331
|
-
lines.push(`
|
|
337
|
+
lines.push(` > ${this.inputBuffer}█`);
|
|
332
338
|
lines.push('');
|
|
333
|
-
if (
|
|
334
|
-
lines.push(s.error(
|
|
339
|
+
if (this.error) {
|
|
340
|
+
lines.push(` ${s.error(this.error)}`);
|
|
335
341
|
}
|
|
336
342
|
else {
|
|
337
|
-
lines.push(s.muted('
|
|
343
|
+
lines.push(` ${s.muted('Keep it concise (2-100 characters)')}`);
|
|
338
344
|
}
|
|
339
345
|
break;
|
|
340
346
|
case 3: // Description
|
|
341
|
-
lines.push(chalk.bold('
|
|
347
|
+
lines.push(` ${chalk.bold('Description (details)')}`);
|
|
342
348
|
lines.push('');
|
|
343
|
-
lines.push(`
|
|
349
|
+
lines.push(` > ${this.inputBuffer}█`);
|
|
344
350
|
lines.push('');
|
|
345
|
-
if (
|
|
346
|
-
lines.push(s.error(
|
|
351
|
+
if (this.error) {
|
|
352
|
+
lines.push(` ${s.error(this.error)}`);
|
|
347
353
|
}
|
|
348
354
|
else {
|
|
349
|
-
lines.push(s.muted('
|
|
355
|
+
lines.push(` ${s.muted('Explain the requirement or issue')}`);
|
|
350
356
|
}
|
|
351
357
|
break;
|
|
352
358
|
case 4: // Confirm
|
|
353
|
-
lines.push(chalk.bold('
|
|
359
|
+
lines.push(` ${chalk.bold('Confirm new item:')}`);
|
|
354
360
|
lines.push('');
|
|
355
|
-
lines.push(s.muted('
|
|
356
|
-
lines.push(s.muted('
|
|
357
|
-
lines.push(s.muted('
|
|
358
|
-
lines.push(s.muted('
|
|
361
|
+
lines.push(` ${s.muted('Type:')} ${this.newItem.type ? TYPE_LABELS[this.newItem.type] : 'N/A'}`);
|
|
362
|
+
lines.push(` ${s.muted('Priority:')} ${this.newItem.priority ? PRIORITY_LABELS[this.newItem.priority] : 'N/A'}`);
|
|
363
|
+
lines.push(` ${s.muted('Title:')} ${this.newItem.title}`);
|
|
364
|
+
lines.push(` ${s.muted('Description:')} ${this.newItem.description}`);
|
|
359
365
|
lines.push('');
|
|
360
366
|
for (let i = 0; i < 2; i++) {
|
|
361
|
-
const isSelected =
|
|
362
|
-
const prefix = isSelected ? '
|
|
367
|
+
const isSelected = this.selectedIndex === i;
|
|
368
|
+
const prefix = isSelected ? s.primary('❯ ') : ' ';
|
|
363
369
|
const label = i === 0 ? '1. Create item' : '2. Cancel';
|
|
364
|
-
lines.push(isSelected ? s.primary(
|
|
370
|
+
lines.push(` ${prefix}${isSelected ? s.primary(label) : s.muted(label)}`);
|
|
365
371
|
}
|
|
366
372
|
break;
|
|
367
373
|
}
|
|
368
374
|
lines.push('');
|
|
369
375
|
lines.push(border);
|
|
370
|
-
if (
|
|
371
|
-
lines.push(s.muted('
|
|
376
|
+
if (this.step === 2 || this.step === 3) {
|
|
377
|
+
lines.push(` ${s.muted('Enter Confirm · Esc Back')}`);
|
|
372
378
|
}
|
|
373
379
|
else {
|
|
374
|
-
lines.push(s.muted('
|
|
380
|
+
lines.push(` ${s.muted('↑↓/jk Navigate · Enter Select · Esc Back')}`);
|
|
375
381
|
}
|
|
382
|
+
lines.push(border);
|
|
383
|
+
return lines;
|
|
376
384
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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;
|
|
385
|
+
handleKey(data) {
|
|
386
|
+
const char = extractPrintable(data);
|
|
387
|
+
// Ctrl+C closes everything
|
|
388
|
+
if (isCtrlC(data)) {
|
|
389
|
+
return closeOverlay({ modified: this.editState.modified });
|
|
401
390
|
}
|
|
402
|
-
|
|
403
|
-
|
|
391
|
+
// Text input steps (title and description)
|
|
392
|
+
if (this.step === 2 || this.step === 3) {
|
|
393
|
+
if (isEscape(data)) {
|
|
394
|
+
if (this.step === 2) {
|
|
395
|
+
this.step = 1;
|
|
396
|
+
this.selectedIndex = 0;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
this.step = 2;
|
|
400
|
+
this.inputBuffer = this.newItem.title;
|
|
401
|
+
}
|
|
402
|
+
this.error = null;
|
|
403
|
+
return stay();
|
|
404
|
+
}
|
|
405
|
+
if (isEnter(data)) {
|
|
406
|
+
const text = this.inputBuffer.trim();
|
|
407
|
+
if (this.step === 2) {
|
|
408
|
+
if (text.length < 2) {
|
|
409
|
+
this.error = 'Title too short (min 2 characters)';
|
|
410
|
+
}
|
|
411
|
+
else if (text.length > 100) {
|
|
412
|
+
this.error = 'Title too long (max 100 characters)';
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.newItem.title = text;
|
|
416
|
+
this.step = 3;
|
|
417
|
+
this.inputBuffer = '';
|
|
418
|
+
this.error = null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
if (text.length < 5) {
|
|
423
|
+
this.error = 'Description too short (min 5 characters)';
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
this.newItem.description = text;
|
|
427
|
+
this.step = 4;
|
|
428
|
+
this.selectedIndex = 0;
|
|
429
|
+
this.error = null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return stay();
|
|
433
|
+
}
|
|
434
|
+
if (isBackspace(data)) {
|
|
435
|
+
this.inputBuffer = this.inputBuffer.slice(0, -1);
|
|
436
|
+
return stay();
|
|
437
|
+
}
|
|
438
|
+
if (char) {
|
|
439
|
+
this.inputBuffer += char;
|
|
440
|
+
return stay();
|
|
441
|
+
}
|
|
442
|
+
return stay(false);
|
|
404
443
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
444
|
+
// Selection steps (type, priority, confirm)
|
|
445
|
+
if (isEscape(data)) {
|
|
446
|
+
if (this.step === 0) {
|
|
447
|
+
return popScreen();
|
|
448
|
+
}
|
|
449
|
+
else if (this.step === 4) {
|
|
450
|
+
this.step = 3;
|
|
451
|
+
this.inputBuffer = this.newItem.description;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
this.step = (this.step - 1);
|
|
455
|
+
this.selectedIndex = 0;
|
|
456
|
+
}
|
|
457
|
+
return stay();
|
|
408
458
|
}
|
|
459
|
+
if (isNavigateUp(data)) {
|
|
460
|
+
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
461
|
+
return stay();
|
|
462
|
+
}
|
|
463
|
+
if (isNavigateDown(data)) {
|
|
464
|
+
const maxIdx = this.getMaxIndexForStep();
|
|
465
|
+
this.selectedIndex = Math.min(maxIdx, this.selectedIndex + 1);
|
|
466
|
+
return stay();
|
|
467
|
+
}
|
|
468
|
+
if (isEnter(data)) {
|
|
469
|
+
switch (this.step) {
|
|
470
|
+
case 0:
|
|
471
|
+
this.newItem.type = TYPE_OPTIONS[this.selectedIndex];
|
|
472
|
+
this.step = 1;
|
|
473
|
+
this.selectedIndex = 0;
|
|
474
|
+
break;
|
|
475
|
+
case 1:
|
|
476
|
+
this.newItem.priority = PRIORITY_OPTIONS[this.selectedIndex];
|
|
477
|
+
this.step = 2;
|
|
478
|
+
this.inputBuffer = '';
|
|
479
|
+
break;
|
|
480
|
+
case 4:
|
|
481
|
+
if (this.newItem.type && this.newItem.priority && this.editState.projectId) {
|
|
482
|
+
if (this.selectedIndex === 0) {
|
|
483
|
+
// Create item in database
|
|
484
|
+
createItem(this.editState.projectId, this.newItem.type, this.newItem.priority, this.newItem.title, this.newItem.description);
|
|
485
|
+
this.editState.modified = true;
|
|
486
|
+
this.editState.reloadItems();
|
|
487
|
+
}
|
|
488
|
+
return popScreen();
|
|
489
|
+
}
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
return stay();
|
|
493
|
+
}
|
|
494
|
+
// Number keys for quick selection
|
|
495
|
+
if (char && char >= '1' && char <= '9') {
|
|
496
|
+
const numIdx = parseInt(char, 10) - 1;
|
|
497
|
+
const maxIdx = this.getMaxIndexForStep();
|
|
498
|
+
if (numIdx <= maxIdx) {
|
|
499
|
+
this.selectedIndex = numIdx;
|
|
500
|
+
return stay();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return stay(false);
|
|
409
504
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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;
|
|
505
|
+
getMaxIndexForStep() {
|
|
506
|
+
switch (this.step) {
|
|
507
|
+
case 0: return TYPE_OPTIONS.length - 1;
|
|
508
|
+
case 1: return PRIORITY_OPTIONS.length - 1;
|
|
509
|
+
case 4: return 1;
|
|
510
|
+
default: return 0;
|
|
511
|
+
}
|
|
421
512
|
}
|
|
422
513
|
}
|
|
423
514
|
// =============================================================================
|
|
424
|
-
//
|
|
515
|
+
// Backlog Overlay
|
|
425
516
|
// =============================================================================
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
items
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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;
|
|
517
|
+
class BacklogOverlay extends TabbedListOverlay {
|
|
518
|
+
editState;
|
|
519
|
+
constructor() {
|
|
520
|
+
const activeProject = getActiveProject();
|
|
521
|
+
const projectId = activeProject?.id ?? null;
|
|
522
|
+
// Load items from database (empty if no project)
|
|
523
|
+
const items = projectId ? loadItemsFromDb(projectId) : [];
|
|
524
|
+
const config = {
|
|
525
|
+
title: 'Backlog',
|
|
526
|
+
tabs: TABS,
|
|
527
|
+
items,
|
|
528
|
+
pageSize: PAGE_SIZE,
|
|
529
|
+
detailScreensFullScreen: false, // Render screens in-place
|
|
530
|
+
filterByTab: (item, tabId) => {
|
|
531
|
+
if (tabId === 'all')
|
|
532
|
+
return true;
|
|
533
|
+
if (tabId === 'active') {
|
|
534
|
+
return item.status === '📋' || item.status === '🚧';
|
|
559
535
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
536
|
+
return item.type === tabId;
|
|
537
|
+
},
|
|
538
|
+
getSearchText: (item) => `${item.id} ${item.title} ${item.description}`,
|
|
539
|
+
renderItem: (item, isSelected, styles) => {
|
|
540
|
+
const cols = terminal.getTerminalWidth();
|
|
541
|
+
const prefix = isSelected ? styles.primary('❯ ') : ' ';
|
|
542
|
+
const idW = 10;
|
|
543
|
+
const typeW = 10;
|
|
544
|
+
const statusW = 4;
|
|
545
|
+
const priW = 8;
|
|
546
|
+
const titleW = Math.max(20, cols - idW - typeW - statusW - priW - 10);
|
|
547
|
+
const row = `${item.id.padEnd(idW)}${item.type.padEnd(typeW)}${item.status.padEnd(statusW)}${item.priority.padEnd(priW)}${item.title.slice(0, titleW - 2)}`;
|
|
548
|
+
if (isSelected) {
|
|
549
|
+
return `${prefix}${styles.primary(row)}`;
|
|
564
550
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
551
|
+
return `${prefix}${styles.muted(row)}`;
|
|
552
|
+
},
|
|
553
|
+
showCount: true,
|
|
554
|
+
emptyMessage: projectId === null
|
|
555
|
+
? 'No active project. Use /projects to select one.'
|
|
556
|
+
: 'No items found. Use [a] to add or /design to create initial backlog.',
|
|
557
|
+
noResultsMessage: 'No items match the search.',
|
|
558
|
+
renderSelectedPreview: (item, styles) => {
|
|
559
|
+
const cols = terminal.getTerminalWidth();
|
|
560
|
+
const descPrefix = ' Desc: ';
|
|
561
|
+
const maxDescLen = cols - descPrefix.length - 2;
|
|
562
|
+
const truncatedDesc = item.description.length > maxDescLen
|
|
563
|
+
? item.description.slice(0, maxDescLen - 3) + '...'
|
|
564
|
+
: item.description;
|
|
565
|
+
const lines = [styles.muted(descPrefix) + truncatedDesc];
|
|
566
|
+
if (item.commit) {
|
|
567
|
+
lines.push(styles.muted(' Commit: ') + item.commit);
|
|
571
568
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
return;
|
|
569
|
+
return lines;
|
|
570
|
+
},
|
|
571
|
+
footerHints: (searchMode) => {
|
|
572
|
+
if (searchMode) {
|
|
573
|
+
return 'Type to search · Enter/Esc Exit search · Backspace Delete';
|
|
577
574
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
575
|
+
return '/ Search · ↑↓/jk Navigate · ←→/hl Pages · Tab Filter · Space Toggle · a Add · q/Esc Close';
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
super(config);
|
|
579
|
+
// Create edit state that screens can share
|
|
580
|
+
this.editState = {
|
|
581
|
+
modified: false,
|
|
582
|
+
projectId,
|
|
583
|
+
items,
|
|
584
|
+
reloadItems: () => {
|
|
585
|
+
if (this.editState.projectId) {
|
|
586
|
+
this.editState.items = loadItemsFromDb(this.editState.projectId);
|
|
587
|
+
// Update the overlay's internal state
|
|
588
|
+
this.state.items = this.editState.items;
|
|
589
|
+
// Re-apply filters
|
|
590
|
+
const currentTabId = this.listConfig.tabs[this.state.currentTab]?.id ?? 'all';
|
|
591
|
+
let filtered = this.state.items.filter((item) => this.listConfig.filterByTab(item, currentTabId));
|
|
592
|
+
if (this.state.searchQuery) {
|
|
593
|
+
const query = this.state.searchQuery.toLowerCase();
|
|
594
|
+
filtered = filtered.filter((item) => this.listConfig.getSearchText(item).toLowerCase().includes(query));
|
|
587
595
|
}
|
|
588
|
-
|
|
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;
|
|
596
|
+
this.state.filteredItems = filtered;
|
|
608
597
|
}
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
createDetailScreen(item) {
|
|
602
|
+
return new DetailScreen(item, this.editState, this.styles);
|
|
603
|
+
}
|
|
604
|
+
handleKey(data) {
|
|
605
|
+
// Only intercept when on the main list screen
|
|
606
|
+
if (this.screenStack.size() === 1) {
|
|
607
|
+
// Ctrl+C always closes
|
|
608
|
+
if (isCtrlC(data)) {
|
|
609
|
+
this.close({ modified: this.editState.modified });
|
|
609
610
|
return;
|
|
610
611
|
}
|
|
611
|
-
//
|
|
612
|
-
if (state.
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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);
|
|
612
|
+
// Only handle custom keys when not in search mode
|
|
613
|
+
if (!this.state.searchMode) {
|
|
614
|
+
const char = extractPrintable(data);
|
|
615
|
+
// Space - toggle status
|
|
616
|
+
if (isSpace(data) && this.state.filteredItems.length > 0) {
|
|
617
|
+
const item = this.state.filteredItems[this.state.selectedIndex];
|
|
618
|
+
// Cycle through statuses: 📋 → 🚧 → ✅ → 📋
|
|
619
|
+
const statusIdx = STATUS_OPTIONS.indexOf(item.status);
|
|
620
|
+
const newStatus = STATUS_OPTIONS[(statusIdx + 1) % STATUS_OPTIONS.length];
|
|
621
|
+
updateItemStatus(item.dbId, newStatus);
|
|
622
|
+
this.editState.modified = true;
|
|
623
|
+
this.editState.reloadItems();
|
|
624
|
+
this.update();
|
|
630
625
|
return;
|
|
631
626
|
}
|
|
632
|
-
//
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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();
|
|
627
|
+
// 'a' - add mode
|
|
628
|
+
if (char === 'a' || char === 'A') {
|
|
629
|
+
if (this.editState.projectId) {
|
|
630
|
+
const addScreen = new AddScreen(this.editState, this.styles);
|
|
631
|
+
this.screenStack.push(addScreen);
|
|
632
|
+
this.update();
|
|
658
633
|
}
|
|
659
|
-
lineCount = render(state, lineCount);
|
|
660
634
|
return;
|
|
661
635
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
}
|
|
636
|
+
// 'q' or Esc closes
|
|
637
|
+
if (char === 'q' || isEscape(data)) {
|
|
638
|
+
this.close({ modified: this.editState.modified });
|
|
639
|
+
return;
|
|
779
640
|
}
|
|
780
|
-
lineCount = render(state, lineCount);
|
|
781
|
-
return;
|
|
782
641
|
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
|
|
642
|
+
}
|
|
643
|
+
// Let parent handle other keys (tabs, search, pagination, Enter for detail)
|
|
644
|
+
super.handleKey(data);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// =============================================================================
|
|
648
|
+
// Export Function (Backward Compatible)
|
|
649
|
+
// =============================================================================
|
|
650
|
+
export async function showBacklogOverlay() {
|
|
651
|
+
return new BacklogOverlay().show();
|
|
786
652
|
}
|