@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.
Files changed (315) hide show
  1. package/README.md +30 -12
  2. package/dist/agent.d.ts +74 -1
  3. package/dist/agent.js +259 -76
  4. package/dist/anchors/index.d.ts +9 -0
  5. package/dist/anchors/index.js +9 -0
  6. package/dist/anchors/project-anchors.d.ts +79 -0
  7. package/dist/anchors/project-anchors.js +202 -0
  8. package/dist/commands/handler-types.d.ts +68 -0
  9. package/dist/commands/handler-types.js +8 -0
  10. package/dist/commands/handlers/agent-commands.d.ts +13 -0
  11. package/dist/commands/handlers/agent-commands.js +305 -0
  12. package/dist/commands/handlers/design-commands.d.ts +15 -0
  13. package/dist/commands/handlers/design-commands.js +334 -0
  14. package/dist/commands/handlers/index.d.ts +20 -0
  15. package/dist/commands/handlers/index.js +43 -0
  16. package/dist/commands/handlers/overlay-commands.d.ts +21 -0
  17. package/dist/commands/handlers/overlay-commands.js +287 -0
  18. package/dist/commands/handlers/project-commands.d.ts +11 -0
  19. package/dist/commands/handlers/project-commands.js +167 -0
  20. package/dist/commands/handlers/simple-commands.d.ts +19 -0
  21. package/dist/commands/handlers/simple-commands.js +144 -0
  22. package/dist/commands/index.d.ts +2 -1
  23. package/dist/commands/registry.d.ts +50 -0
  24. package/dist/commands/registry.js +75 -0
  25. package/dist/commands-v2/handlers/context.d.ts +13 -0
  26. package/dist/commands-v2/handlers/context.js +348 -0
  27. package/dist/commands-v2/handlers/core.d.ts +13 -0
  28. package/dist/commands-v2/handlers/core.js +165 -0
  29. package/dist/commands-v2/handlers/debug.d.ts +11 -0
  30. package/dist/commands-v2/handlers/debug.js +159 -0
  31. package/dist/commands-v2/handlers/index.d.ts +12 -0
  32. package/dist/commands-v2/handlers/index.js +24 -0
  33. package/dist/commands-v2/handlers/project.d.ts +22 -0
  34. package/dist/commands-v2/handlers/project.js +814 -0
  35. package/dist/commands-v2/handlers/settings.d.ts +15 -0
  36. package/dist/commands-v2/handlers/settings.js +235 -0
  37. package/dist/commands-v2/index.d.ts +13 -0
  38. package/dist/commands-v2/index.js +15 -0
  39. package/dist/commands-v2/registry.d.ts +37 -0
  40. package/dist/commands-v2/registry.js +80 -0
  41. package/dist/commands-v2/types.d.ts +75 -0
  42. package/dist/commands-v2/types.js +7 -0
  43. package/dist/commands.js +110 -7
  44. package/dist/index.js +288 -29
  45. package/dist/input-handlers/index.d.ts +7 -0
  46. package/dist/input-handlers/index.js +7 -0
  47. package/dist/input-handlers/memory-handler.d.ts +26 -0
  48. package/dist/input-handlers/memory-handler.js +68 -0
  49. package/dist/repl-helpers.d.ts +63 -0
  50. package/dist/repl-helpers.js +318 -0
  51. package/dist/repl-v2.d.ts +155 -0
  52. package/dist/repl-v2.js +774 -0
  53. package/dist/repl.d.ts +32 -4
  54. package/dist/repl.js +250 -977
  55. package/dist/settings/index.d.ts +23 -0
  56. package/dist/settings/index.js +48 -0
  57. package/dist/settings/paths.d.ts +110 -0
  58. package/dist/settings/paths.js +264 -0
  59. package/dist/templates/compilr-md.js +7 -4
  60. package/dist/templates/index.js +3 -4
  61. package/dist/themes/colors.js +3 -1
  62. package/dist/themes/registry.d.ts +5 -36
  63. package/dist/themes/registry.js +11 -95
  64. package/dist/themes/types.d.ts +3 -38
  65. package/dist/themes/types.js +2 -2
  66. package/dist/tools/anchor-tools.d.ts +31 -0
  67. package/dist/tools/anchor-tools.js +255 -0
  68. package/dist/tools/backlog-wrappers.d.ts +54 -0
  69. package/dist/tools/backlog-wrappers.js +338 -0
  70. package/dist/tools/backlog.js +1 -1
  71. package/dist/tools/db-tools.d.ts +65 -0
  72. package/dist/tools/db-tools.js +19 -0
  73. package/dist/tools/document-db.d.ts +43 -0
  74. package/dist/tools/document-db.js +220 -0
  75. package/dist/tools/project-db.d.ts +102 -0
  76. package/dist/tools/project-db.js +370 -0
  77. package/dist/tools/workitem-db.d.ts +103 -0
  78. package/dist/tools/workitem-db.js +549 -0
  79. package/dist/tools.js +13 -3
  80. package/dist/ui/agents-overlay-v2.d.ts +43 -0
  81. package/dist/ui/agents-overlay-v2.js +809 -0
  82. package/dist/ui/agents-overlay.d.ts +5 -5
  83. package/dist/ui/agents-overlay.js +782 -420
  84. package/dist/ui/anchors-overlay.d.ts +12 -0
  85. package/dist/ui/anchors-overlay.js +775 -0
  86. package/dist/ui/arch-type-overlay.d.ts +1 -6
  87. package/dist/ui/arch-type-overlay.js +175 -203
  88. package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
  89. package/dist/ui/ask-user-overlay-v2.js +555 -0
  90. package/dist/ui/ask-user-overlay.d.ts +2 -2
  91. package/dist/ui/ask-user-overlay.js +443 -535
  92. package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
  93. package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
  94. package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
  95. package/dist/ui/ask-user-simple-overlay.js +182 -209
  96. package/dist/ui/backlog-overlay.d.ts +16 -1
  97. package/dist/ui/backlog-overlay.js +525 -659
  98. package/dist/ui/base/index.d.ts +26 -0
  99. package/dist/ui/base/index.js +33 -0
  100. package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
  101. package/dist/ui/base/inline-overlay-utils.js +320 -0
  102. package/dist/ui/base/inline-overlay.d.ts +159 -0
  103. package/dist/ui/base/inline-overlay.js +257 -0
  104. package/dist/ui/base/key-utils.d.ts +15 -0
  105. package/dist/ui/base/key-utils.js +30 -0
  106. package/dist/ui/base/overlay-base-v2.d.ts +193 -0
  107. package/dist/ui/base/overlay-base-v2.js +246 -0
  108. package/dist/ui/base/overlay-base.d.ts +156 -0
  109. package/dist/ui/base/overlay-base.js +238 -0
  110. package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
  111. package/dist/ui/base/overlay-lifecycle.js +159 -0
  112. package/dist/ui/base/overlay-types.d.ts +185 -0
  113. package/dist/ui/base/overlay-types.js +7 -0
  114. package/dist/ui/base/render-utils.d.ts +8 -0
  115. package/dist/ui/base/render-utils.js +11 -0
  116. package/dist/ui/base/screen-stack.d.ts +148 -0
  117. package/dist/ui/base/screen-stack.js +184 -0
  118. package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
  119. package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
  120. package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
  121. package/dist/ui/base/tabbed-list-overlay.js +369 -0
  122. package/dist/ui/commands-overlay-v2.d.ts +33 -0
  123. package/dist/ui/commands-overlay-v2.js +441 -0
  124. package/dist/ui/commands-overlay.d.ts +7 -2
  125. package/dist/ui/commands-overlay.js +384 -355
  126. package/dist/ui/config-overlay.d.ts +5 -4
  127. package/dist/ui/config-overlay.js +243 -513
  128. package/dist/ui/conversation.d.ts +75 -4
  129. package/dist/ui/conversation.js +374 -161
  130. package/dist/ui/docs-overlay.d.ts +17 -0
  131. package/dist/ui/docs-overlay.js +303 -0
  132. package/dist/ui/ephemeral.d.ts +1 -1
  133. package/dist/ui/ephemeral.js +1 -1
  134. package/dist/ui/features/index.d.ts +34 -0
  135. package/dist/ui/features/index.js +34 -0
  136. package/dist/ui/features/input-feature.d.ts +85 -0
  137. package/dist/ui/features/input-feature.js +238 -0
  138. package/dist/ui/features/list-feature.d.ts +155 -0
  139. package/dist/ui/features/list-feature.js +244 -0
  140. package/dist/ui/features/pagination-feature.d.ts +154 -0
  141. package/dist/ui/features/pagination-feature.js +238 -0
  142. package/dist/ui/features/search-feature.d.ts +148 -0
  143. package/dist/ui/features/search-feature.js +185 -0
  144. package/dist/ui/features/tab-feature.d.ts +194 -0
  145. package/dist/ui/features/tab-feature.js +307 -0
  146. package/dist/ui/footer-v2.d.ts +222 -0
  147. package/dist/ui/footer-v2.js +1349 -0
  148. package/dist/ui/footer.d.ts +107 -0
  149. package/dist/ui/footer.js +359 -67
  150. package/dist/ui/guardrail-overlay.d.ts +29 -0
  151. package/dist/ui/guardrail-overlay.js +145 -0
  152. package/dist/ui/help-overlay-v2.d.ts +34 -0
  153. package/dist/ui/help-overlay-v2.js +309 -0
  154. package/dist/ui/help-overlay.d.ts +16 -0
  155. package/dist/ui/help-overlay.js +316 -0
  156. package/dist/ui/index.d.ts +1 -1
  157. package/dist/ui/index.js +1 -3
  158. package/dist/ui/init-overlay-v2.d.ts +34 -0
  159. package/dist/ui/init-overlay-v2.js +600 -0
  160. package/dist/ui/init-overlay.d.ts +12 -2
  161. package/dist/ui/init-overlay.js +349 -270
  162. package/dist/ui/input-prompt-v2.d.ts +1 -0
  163. package/dist/ui/input-prompt-v2.js +14 -6
  164. package/dist/ui/input-prompt.d.ts +116 -33
  165. package/dist/ui/input-prompt.js +536 -337
  166. package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
  167. package/dist/ui/iteration-limit-overlay-v2.js +114 -0
  168. package/dist/ui/iteration-limit-overlay.d.ts +2 -2
  169. package/dist/ui/iteration-limit-overlay.js +92 -128
  170. package/dist/ui/keys-overlay-v2.d.ts +41 -0
  171. package/dist/ui/keys-overlay-v2.js +248 -0
  172. package/dist/ui/keys-overlay.d.ts +1 -0
  173. package/dist/ui/keys-overlay.js +203 -141
  174. package/dist/ui/line-utils.d.ts +88 -0
  175. package/dist/ui/line-utils.js +150 -0
  176. package/dist/ui/live-region.d.ts +161 -0
  177. package/dist/ui/live-region.js +387 -0
  178. package/dist/ui/mascot/expressions.d.ts +32 -0
  179. package/dist/ui/mascot/expressions.js +213 -0
  180. package/dist/ui/mascot/index.d.ts +8 -0
  181. package/dist/ui/mascot/index.js +8 -0
  182. package/dist/ui/mascot/renderer.d.ts +19 -0
  183. package/dist/ui/mascot/renderer.js +97 -0
  184. package/dist/ui/mascot-overlay-v2.d.ts +41 -0
  185. package/dist/ui/mascot-overlay-v2.js +138 -0
  186. package/dist/ui/mascot-overlay.d.ts +21 -0
  187. package/dist/ui/mascot-overlay.js +146 -0
  188. package/dist/ui/model-overlay-v2.d.ts +49 -0
  189. package/dist/ui/model-overlay-v2.js +118 -0
  190. package/dist/ui/model-overlay.d.ts +27 -0
  191. package/dist/ui/model-overlay.js +221 -0
  192. package/dist/ui/model-warning-overlay.js +3 -5
  193. package/dist/ui/new-overlay.d.ts +34 -0
  194. package/dist/ui/new-overlay.js +604 -0
  195. package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
  196. package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
  197. package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
  198. package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
  199. package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
  200. package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
  201. package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
  202. package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
  203. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
  204. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
  205. package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
  206. package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
  207. package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
  208. package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
  209. package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
  210. package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
  211. package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
  212. package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
  213. package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
  214. package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
  215. package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
  216. package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
  217. package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
  218. package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
  219. package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
  220. package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
  221. package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
  222. package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
  223. package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
  224. package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
  225. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
  226. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
  227. package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
  228. package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
  229. package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
  230. package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
  231. package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
  232. package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
  233. package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
  234. package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
  235. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
  236. package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
  237. package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
  238. package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
  239. package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
  240. package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
  241. package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
  242. package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
  243. package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
  244. package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
  245. package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
  246. package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
  247. package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
  248. package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
  249. package/dist/ui/overlay/index.d.ts +33 -0
  250. package/dist/ui/overlay/index.js +35 -0
  251. package/dist/ui/overlay/key-utils.d.ts +6 -0
  252. package/dist/ui/overlay/key-utils.js +6 -0
  253. package/dist/ui/overlay/overlay-types.d.ts +128 -0
  254. package/dist/ui/overlay/overlay-types.js +22 -0
  255. package/dist/ui/overlay/types.d.ts +135 -0
  256. package/dist/ui/overlay/types.js +22 -0
  257. package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
  258. package/dist/ui/overlays/help-overlay-v2.js +198 -0
  259. package/dist/ui/overlays/index.d.ts +11 -0
  260. package/dist/ui/overlays/index.js +11 -0
  261. package/dist/ui/overlays.d.ts +0 -4
  262. package/dist/ui/overlays.js +0 -444
  263. package/dist/ui/permission-overlay-v2.d.ts +36 -0
  264. package/dist/ui/permission-overlay-v2.js +380 -0
  265. package/dist/ui/permission-overlay.d.ts +1 -1
  266. package/dist/ui/permission-overlay.js +186 -298
  267. package/dist/ui/projects-overlay.d.ts +19 -0
  268. package/dist/ui/projects-overlay.js +484 -0
  269. package/dist/ui/providers/types.d.ts +178 -0
  270. package/dist/ui/providers/types.js +9 -0
  271. package/dist/ui/render-modes.d.ts +36 -0
  272. package/dist/ui/render-modes.js +44 -0
  273. package/dist/ui/startup-menu.d.ts +36 -0
  274. package/dist/ui/startup-menu.js +236 -0
  275. package/dist/ui/subagent-renderer.d.ts +117 -0
  276. package/dist/ui/subagent-renderer.js +334 -0
  277. package/dist/ui/terminal-codes.d.ts +94 -0
  278. package/dist/ui/terminal-codes.js +124 -0
  279. package/dist/ui/terminal-renderer.d.ts +221 -0
  280. package/dist/ui/terminal-renderer.js +751 -0
  281. package/dist/ui/terminal-ui.d.ts +463 -0
  282. package/dist/ui/terminal-ui.js +2296 -0
  283. package/dist/ui/terminal.d.ts +20 -0
  284. package/dist/ui/terminal.js +72 -0
  285. package/dist/ui/theme-overlay-v2.d.ts +42 -0
  286. package/dist/ui/theme-overlay-v2.js +135 -0
  287. package/dist/ui/theme-overlay.d.ts +24 -0
  288. package/dist/ui/theme-overlay.js +127 -0
  289. package/dist/ui/todo-zone.js +53 -25
  290. package/dist/ui/tool-formatters.d.ts +16 -0
  291. package/dist/ui/tool-formatters.js +516 -0
  292. package/dist/ui/tools-overlay-v2.d.ts +47 -0
  293. package/dist/ui/tools-overlay-v2.js +218 -0
  294. package/dist/ui/tools-overlay.d.ts +10 -2
  295. package/dist/ui/tools-overlay.js +172 -220
  296. package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
  297. package/dist/ui/tutorial-overlay-v2.js +1035 -0
  298. package/dist/ui/tutorial-overlay.d.ts +1 -0
  299. package/dist/ui/tutorial-overlay.js +400 -302
  300. package/dist/ui/workflow-overlay.d.ts +22 -0
  301. package/dist/ui/workflow-overlay.js +636 -0
  302. package/dist/utils/debug-log.d.ts +28 -0
  303. package/dist/utils/debug-log.js +57 -0
  304. package/dist/utils/model-tiers.js +1 -1
  305. package/dist/utils/path-safety.d.ts +56 -0
  306. package/dist/utils/path-safety.js +239 -0
  307. package/dist/workflow/guided-mode-injector.d.ts +42 -0
  308. package/dist/workflow/guided-mode-injector.js +191 -0
  309. package/dist/workflow/index.d.ts +8 -0
  310. package/dist/workflow/index.js +8 -0
  311. package/dist/workflow/step-criteria.d.ts +62 -0
  312. package/dist/workflow/step-criteria.js +150 -0
  313. package/dist/workflow/step-tracker.d.ts +92 -0
  314. package/dist/workflow/step-tracker.js +141 -0
  315. 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
- * Provides a UI for manually adding, editing, and filtering backlog items.
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 { getStyles } from '../themes/index.js';
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
- // Backlog File Operations
48
+ // Status Mapping (Database ↔ Display)
40
49
  // =============================================================================
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
- }
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
- 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`;
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
- function generateId(type, existingItems) {
131
- const prefixMap = {
132
- 'feature': 'REQ',
133
- 'bug': 'BUG',
134
- 'tech-debt': 'TECH',
135
- 'chore': 'CHORE',
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
- // Filtering & Search
89
+ // Database Operations
151
90
  // =============================================================================
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 === '🚧');
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
- else if (state.filterType !== 'all') {
160
- results = results.filter(item => item.type === state.filterType);
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
- // 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));
163
+ if (currentLine.length > 0) {
164
+ lines.push(currentLine);
168
165
  }
169
- state.filteredItems = results;
170
- state.selectedIndex = 0;
171
- state.currentPage = 0;
166
+ return lines.length > 0 ? lines : [''];
172
167
  }
173
168
  // =============================================================================
174
- // Rendering
169
+ // Detail Screen (In-Place Editing)
175
170
  // =============================================================================
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)')}`);
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
- // 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
- }
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
- else {
217
- lines.push(s.muted(` Showing ${String(startIndex + 1)}-${String(endIndex)} of ${String(totalResults)}:`));
216
+ // Commit info
217
+ if (this.item.commit) {
218
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
- }
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
- if (state.isSearching) {
264
- lines.push(s.muted(' Type to search · Enter/Esc Exit search · Backspace Delete'));
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
- else {
267
- lines.push(s.muted(' / Search · ↑↓ Navigate · ←→ Pages · Tab Filter · Enter Detail · Space Toggle · a Add · Esc Close'));
232
+ // Escape - back to list
233
+ if (isEscape(data)) {
234
+ return popScreen();
268
235
  }
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);
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 (item.commit) {
294
- lines.push('');
295
- lines.push(s.muted(' Commit: ') + item.commit);
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
- lines.push('');
299
- lines.push(border);
300
- lines.push(s.muted(' ↑↓ Select field · ←→ Change value · Esc Back'));
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
- else {
303
- // Add mode rendering
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(` Step ${String(state.addStep + 1)}/5: ${stepLabels[state.addStep]}`));
311
+ lines.push(` ${s.muted(`Step ${String(this.step + 1)}/5: ${stepLabels[this.step]}`)}`);
306
312
  lines.push('');
307
- switch (state.addStep) {
313
+ switch (this.step) {
308
314
  case 0: // Type
309
- lines.push(chalk.bold(' What type of item?'));
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 = state.selectedIndex === i;
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(prefix + label) : s.muted(prefix + label));
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(' Priority level?'));
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 = state.selectedIndex === i;
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(prefix + label) : s.muted(prefix + label));
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(' Title (short description)'));
335
+ lines.push(` ${chalk.bold('Title (short description)')}`);
330
336
  lines.push('');
331
- lines.push(` > ${state.inputBuffer}█`);
337
+ lines.push(` > ${this.inputBuffer}█`);
332
338
  lines.push('');
333
- if (state.error) {
334
- lines.push(s.error(` ${state.error}`));
339
+ if (this.error) {
340
+ lines.push(` ${s.error(this.error)}`);
335
341
  }
336
342
  else {
337
- lines.push(s.muted(' Keep it concise (2-100 characters)'));
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(' Description (details)'));
347
+ lines.push(` ${chalk.bold('Description (details)')}`);
342
348
  lines.push('');
343
- lines.push(` > ${state.inputBuffer}█`);
349
+ lines.push(` > ${this.inputBuffer}█`);
344
350
  lines.push('');
345
- if (state.error) {
346
- lines.push(s.error(` ${state.error}`));
351
+ if (this.error) {
352
+ lines.push(` ${s.error(this.error)}`);
347
353
  }
348
354
  else {
349
- lines.push(s.muted(' Explain the requirement or issue'));
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(' Confirm new item:'));
359
+ lines.push(` ${chalk.bold('Confirm new item:')}`);
354
360
  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);
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 = state.selectedIndex === i;
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(prefix + label) : s.muted(prefix + label));
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 (state.addStep === 2 || state.addStep === 3) {
371
- lines.push(s.muted(' Enter Confirm · Esc Go back'));
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(' ↑↓ Navigate · Enter Select · Esc Go back'));
380
+ lines.push(` ${s.muted('↑↓/jk Navigate · Enter Select · Esc Back')}`);
375
381
  }
382
+ lines.push(border);
383
+ return lines;
376
384
  }
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;
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
- else if (currentLine.length + 1 + word.length <= maxWidth) {
403
- currentLine += ' ' + word;
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
- else {
406
- lines.push(currentLine);
407
- currentLine = word;
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
- 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;
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
- // Main Export
515
+ // Backlog Overlay
425
516
  // =============================================================================
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;
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
- if (isDownArrow && state.selectedIndex < pageItems.length - 1) {
561
- state.selectedIndex++;
562
- lineCount = render(state, lineCount);
563
- return;
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
- // Page navigation
566
- if (isLeftArrow && state.currentPage > 0) {
567
- state.currentPage--;
568
- state.selectedIndex = 0;
569
- lineCount = render(state, lineCount);
570
- return;
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
- if (isRightArrow && state.currentPage < totalPages - 1) {
573
- state.currentPage++;
574
- state.selectedIndex = 0;
575
- lineCount = render(state, lineCount);
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
- // 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();
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
- 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;
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
- // ===== 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);
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
- // 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();
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
- 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
- }
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
- process.stdin.on('data', handleData);
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
  }