@butlerw/vellum 0.1.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 (446) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +411 -0
  3. package/__fixtures__/responses/code-generation.json +42 -0
  4. package/__fixtures__/responses/error-response.json +20 -0
  5. package/__fixtures__/responses/hello-world.json +32 -0
  6. package/dist/auth-6MCXESOH.js +26 -0
  7. package/dist/chunk-SECXJGWA.js +597 -0
  8. package/dist/index.js +34023 -0
  9. package/package.json +67 -0
  10. package/src/__tests__/commands.e2e.test.ts +728 -0
  11. package/src/__tests__/credentials.test.ts +713 -0
  12. package/src/__tests__/mode-e2e.test.ts +391 -0
  13. package/src/__tests__/tui-integration.test.tsx +1271 -0
  14. package/src/agents/__tests__/task-persistence.test.ts +235 -0
  15. package/src/agents/commands/delegate.ts +240 -0
  16. package/src/agents/commands/index.ts +10 -0
  17. package/src/agents/commands/resume.ts +335 -0
  18. package/src/agents/index.ts +29 -0
  19. package/src/agents/task-persistence.ts +272 -0
  20. package/src/agents/task-resumption.ts +242 -0
  21. package/src/app.tsx +4737 -0
  22. package/src/commands/__tests__/.gitkeep +1 -0
  23. package/src/commands/__tests__/agents.test.ts +606 -0
  24. package/src/commands/__tests__/auth.test.ts +626 -0
  25. package/src/commands/__tests__/autocomplete.test.ts +683 -0
  26. package/src/commands/__tests__/batch.test.ts +287 -0
  27. package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
  28. package/src/commands/__tests__/completion.test.ts +238 -0
  29. package/src/commands/__tests__/core.test.ts +363 -0
  30. package/src/commands/__tests__/executor.test.ts +496 -0
  31. package/src/commands/__tests__/exit-codes.test.ts +220 -0
  32. package/src/commands/__tests__/init.test.ts +243 -0
  33. package/src/commands/__tests__/language.test.ts +353 -0
  34. package/src/commands/__tests__/mode-cli.test.ts +667 -0
  35. package/src/commands/__tests__/model.test.ts +277 -0
  36. package/src/commands/__tests__/parser.test.ts +493 -0
  37. package/src/commands/__tests__/performance.bench.ts +380 -0
  38. package/src/commands/__tests__/registry.test.ts +534 -0
  39. package/src/commands/__tests__/resume.test.ts +449 -0
  40. package/src/commands/__tests__/security.test.ts +845 -0
  41. package/src/commands/__tests__/stream-json.test.ts +372 -0
  42. package/src/commands/__tests__/user-commands.test.ts +597 -0
  43. package/src/commands/adapters.ts +267 -0
  44. package/src/commands/agent.ts +395 -0
  45. package/src/commands/agents/generate.ts +506 -0
  46. package/src/commands/agents/index.ts +272 -0
  47. package/src/commands/agents/show.ts +271 -0
  48. package/src/commands/agents/validate.ts +387 -0
  49. package/src/commands/auth.ts +883 -0
  50. package/src/commands/autocomplete.ts +480 -0
  51. package/src/commands/batch/command.ts +388 -0
  52. package/src/commands/batch/executor.ts +361 -0
  53. package/src/commands/batch/index.ts +12 -0
  54. package/src/commands/commit.ts +235 -0
  55. package/src/commands/completion/index.ts +371 -0
  56. package/src/commands/condense.ts +191 -0
  57. package/src/commands/config.ts +344 -0
  58. package/src/commands/context-provider.ts +173 -0
  59. package/src/commands/copy.ts +329 -0
  60. package/src/commands/core/clear.ts +38 -0
  61. package/src/commands/core/exit.ts +43 -0
  62. package/src/commands/core/help.ts +354 -0
  63. package/src/commands/core/index.ts +15 -0
  64. package/src/commands/cost.ts +179 -0
  65. package/src/commands/credentials.tsx +618 -0
  66. package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
  67. package/src/commands/custom-agents/create.ts +377 -0
  68. package/src/commands/custom-agents/export.ts +135 -0
  69. package/src/commands/custom-agents/import.ts +199 -0
  70. package/src/commands/custom-agents/index.ts +372 -0
  71. package/src/commands/custom-agents/info.ts +318 -0
  72. package/src/commands/custom-agents/list.ts +267 -0
  73. package/src/commands/custom-agents/validate.ts +388 -0
  74. package/src/commands/diff-mode.ts +241 -0
  75. package/src/commands/env.ts +53 -0
  76. package/src/commands/executor.ts +579 -0
  77. package/src/commands/exit-codes.ts +202 -0
  78. package/src/commands/index.ts +701 -0
  79. package/src/commands/init/index.ts +15 -0
  80. package/src/commands/init/prompts.ts +366 -0
  81. package/src/commands/init/templates/commands-readme.md +80 -0
  82. package/src/commands/init/templates/example-command.md +79 -0
  83. package/src/commands/init/templates/example-skill.md +168 -0
  84. package/src/commands/init/templates/example-workflow.md +101 -0
  85. package/src/commands/init/templates/prompts-readme.md +52 -0
  86. package/src/commands/init/templates/rules-readme.md +63 -0
  87. package/src/commands/init/templates/skills-readme.md +83 -0
  88. package/src/commands/init/templates/workflows-readme.md +94 -0
  89. package/src/commands/init.ts +391 -0
  90. package/src/commands/install.ts +90 -0
  91. package/src/commands/language.ts +191 -0
  92. package/src/commands/loaders/.gitkeep +1 -0
  93. package/src/commands/lsp.ts +199 -0
  94. package/src/commands/markdown-commands.ts +253 -0
  95. package/src/commands/mcp.ts +588 -0
  96. package/src/commands/memory/export.ts +341 -0
  97. package/src/commands/memory/index.ts +148 -0
  98. package/src/commands/memory/list.ts +261 -0
  99. package/src/commands/memory/search.ts +346 -0
  100. package/src/commands/memory/utils.ts +15 -0
  101. package/src/commands/metrics.ts +75 -0
  102. package/src/commands/migrate/index.ts +16 -0
  103. package/src/commands/migrate/prompts.ts +477 -0
  104. package/src/commands/mode.ts +331 -0
  105. package/src/commands/model.ts +298 -0
  106. package/src/commands/onboard.ts +205 -0
  107. package/src/commands/open.ts +169 -0
  108. package/src/commands/output/stream-json.ts +373 -0
  109. package/src/commands/parser/chain-parser.ts +370 -0
  110. package/src/commands/parser/index.ts +29 -0
  111. package/src/commands/parser/pipe-parser.ts +480 -0
  112. package/src/commands/parser.ts +588 -0
  113. package/src/commands/persistence.ts +355 -0
  114. package/src/commands/progress.ts +18 -0
  115. package/src/commands/prompt/index.ts +17 -0
  116. package/src/commands/prompt/validate.ts +621 -0
  117. package/src/commands/prompt-priority.ts +401 -0
  118. package/src/commands/registry.ts +374 -0
  119. package/src/commands/sandbox/index.ts +131 -0
  120. package/src/commands/security/index.ts +21 -0
  121. package/src/commands/security/input-sanitizer.ts +168 -0
  122. package/src/commands/security/permission-checker.ts +456 -0
  123. package/src/commands/security/sensitive-data.ts +350 -0
  124. package/src/commands/session/delete.ts +38 -0
  125. package/src/commands/session/export.ts +39 -0
  126. package/src/commands/session/index.ts +26 -0
  127. package/src/commands/session/list.ts +26 -0
  128. package/src/commands/session/resume.ts +562 -0
  129. package/src/commands/session/search.ts +434 -0
  130. package/src/commands/session/show.ts +26 -0
  131. package/src/commands/settings.ts +368 -0
  132. package/src/commands/setup.ts +23 -0
  133. package/src/commands/shell/index.ts +16 -0
  134. package/src/commands/shell/setup.ts +422 -0
  135. package/src/commands/shell-init.ts +50 -0
  136. package/src/commands/shell-integration/index.ts +194 -0
  137. package/src/commands/skill.ts +1220 -0
  138. package/src/commands/spec.ts +558 -0
  139. package/src/commands/status.ts +246 -0
  140. package/src/commands/theme.ts +211 -0
  141. package/src/commands/think.ts +551 -0
  142. package/src/commands/trust.ts +211 -0
  143. package/src/commands/tutorial.ts +522 -0
  144. package/src/commands/types.ts +512 -0
  145. package/src/commands/update.ts +274 -0
  146. package/src/commands/usage.ts +213 -0
  147. package/src/commands/user-commands.ts +630 -0
  148. package/src/commands/utils.ts +142 -0
  149. package/src/commands/vim.ts +152 -0
  150. package/src/commands/workflow.ts +257 -0
  151. package/src/components/header.tsx +25 -0
  152. package/src/components/input.tsx +25 -0
  153. package/src/components/message-list.tsx +32 -0
  154. package/src/components/status-bar.tsx +23 -0
  155. package/src/index.tsx +614 -0
  156. package/src/onboarding/__tests__/tutorial.test.ts +740 -0
  157. package/src/onboarding/index.ts +69 -0
  158. package/src/onboarding/tips/index.ts +9 -0
  159. package/src/onboarding/tips/tip-engine.ts +459 -0
  160. package/src/onboarding/tutorial/index.ts +88 -0
  161. package/src/onboarding/tutorial/lessons/basics.ts +151 -0
  162. package/src/onboarding/tutorial/lessons/index.ts +151 -0
  163. package/src/onboarding/tutorial/lessons/modes.ts +230 -0
  164. package/src/onboarding/tutorial/lessons/tools.ts +172 -0
  165. package/src/onboarding/tutorial/progress-tracker.ts +350 -0
  166. package/src/onboarding/tutorial/storage.ts +249 -0
  167. package/src/onboarding/tutorial/tutorial-system.ts +462 -0
  168. package/src/onboarding/tutorial/types.ts +310 -0
  169. package/src/orchestrator-singleton.ts +129 -0
  170. package/src/shutdown.ts +33 -0
  171. package/src/test/e2e/assertions.ts +267 -0
  172. package/src/test/e2e/fixtures.ts +204 -0
  173. package/src/test/e2e/harness.ts +575 -0
  174. package/src/test/e2e/index.ts +57 -0
  175. package/src/test/e2e/types.ts +228 -0
  176. package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
  177. package/src/test/fixtures/fake-response-loader.ts +314 -0
  178. package/src/test/fixtures/index.ts +20 -0
  179. package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
  180. package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
  181. package/src/tui/__tests__/mode-components.test.tsx +395 -0
  182. package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
  183. package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
  184. package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
  185. package/src/tui/adapters/agent-adapter.ts +1008 -0
  186. package/src/tui/adapters/index.ts +48 -0
  187. package/src/tui/adapters/message-adapter.ts +315 -0
  188. package/src/tui/adapters/persistence-bridge.ts +331 -0
  189. package/src/tui/adapters/session-adapter.ts +419 -0
  190. package/src/tui/buffered-stdout.ts +223 -0
  191. package/src/tui/components/AgentProgress.tsx +424 -0
  192. package/src/tui/components/Banner/AsciiArt.ts +160 -0
  193. package/src/tui/components/Banner/Banner.tsx +355 -0
  194. package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
  195. package/src/tui/components/Banner/ShimmerText.tsx +193 -0
  196. package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
  197. package/src/tui/components/Banner/index.ts +61 -0
  198. package/src/tui/components/Banner/useShimmer.ts +241 -0
  199. package/src/tui/components/ChatView.tsx +11 -0
  200. package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
  201. package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
  202. package/src/tui/components/Checkpoint/index.ts +19 -0
  203. package/src/tui/components/CostDisplay.tsx +226 -0
  204. package/src/tui/components/InitErrorBanner.tsx +122 -0
  205. package/src/tui/components/Input/Autocomplete.tsx +603 -0
  206. package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
  207. package/src/tui/components/Input/HighlightedText.tsx +236 -0
  208. package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
  209. package/src/tui/components/Input/TextInput.tsx +1002 -0
  210. package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
  211. package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
  212. package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
  213. package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
  214. package/src/tui/components/Input/highlight.ts +362 -0
  215. package/src/tui/components/Input/index.ts +36 -0
  216. package/src/tui/components/Input/slash-command-utils.ts +135 -0
  217. package/src/tui/components/Layout.tsx +432 -0
  218. package/src/tui/components/McpPanel.tsx +137 -0
  219. package/src/tui/components/MemoryPanel.tsx +448 -0
  220. package/src/tui/components/Messages/CodeBlock.tsx +527 -0
  221. package/src/tui/components/Messages/DiffView.tsx +679 -0
  222. package/src/tui/components/Messages/ImageReference.tsx +89 -0
  223. package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
  224. package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
  225. package/src/tui/components/Messages/MessageBubble.tsx +270 -0
  226. package/src/tui/components/Messages/MessageList.tsx +1719 -0
  227. package/src/tui/components/Messages/StreamingText.tsx +216 -0
  228. package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
  229. package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
  230. package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
  231. package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
  232. package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
  233. package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
  234. package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
  235. package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
  236. package/src/tui/components/Messages/index.ts +25 -0
  237. package/src/tui/components/ModeIndicator.tsx +177 -0
  238. package/src/tui/components/ModeSelector.tsx +216 -0
  239. package/src/tui/components/ModelSelector.tsx +339 -0
  240. package/src/tui/components/OnboardingWizard.tsx +670 -0
  241. package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
  242. package/src/tui/components/RateLimitIndicator.tsx +82 -0
  243. package/src/tui/components/ScreenReaderLayout.tsx +295 -0
  244. package/src/tui/components/SettingsPanel.tsx +643 -0
  245. package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
  246. package/src/tui/components/Sidebar/index.ts +9 -0
  247. package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
  248. package/src/tui/components/Status/index.ts +12 -0
  249. package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
  250. package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
  251. package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
  252. package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
  253. package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
  254. package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
  255. package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
  256. package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
  257. package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
  258. package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
  259. package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
  260. package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
  261. package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
  262. package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
  263. package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
  264. package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
  265. package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
  266. package/src/tui/components/StatusBar/index.ts +60 -0
  267. package/src/tui/components/TipBanner.tsx +115 -0
  268. package/src/tui/components/TodoItem.tsx +208 -0
  269. package/src/tui/components/TodoPanel.tsx +455 -0
  270. package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
  271. package/src/tui/components/Tools/OptionSelector.tsx +160 -0
  272. package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
  273. package/src/tui/components/Tools/ToolParams.tsx +483 -0
  274. package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
  275. package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
  276. package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
  277. package/src/tui/components/Tools/index.ts +21 -0
  278. package/src/tui/components/TrustPrompt.tsx +279 -0
  279. package/src/tui/components/UpdateBanner.tsx +166 -0
  280. package/src/tui/components/VimModeIndicator.tsx +112 -0
  281. package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
  282. package/src/tui/components/backtrack/index.ts +13 -0
  283. package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
  284. package/src/tui/components/common/CostWarning.tsx +294 -0
  285. package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
  286. package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
  287. package/src/tui/components/common/ErrorBoundary.tsx +140 -0
  288. package/src/tui/components/common/GradientText.tsx +224 -0
  289. package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
  290. package/src/tui/components/common/HotkeyHints.tsx +70 -0
  291. package/src/tui/components/common/MaxSizedBox.tsx +354 -0
  292. package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
  293. package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
  294. package/src/tui/components/common/ScrollIndicator.tsx +160 -0
  295. package/src/tui/components/common/Spinner.tsx +342 -0
  296. package/src/tui/components/common/StreamingIndicator.tsx +316 -0
  297. package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
  298. package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
  299. package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
  300. package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
  301. package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
  302. package/src/tui/components/common/VirtualizedList/index.ts +30 -0
  303. package/src/tui/components/common/VirtualizedList/types.ts +107 -0
  304. package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
  305. package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
  306. package/src/tui/components/common/index.ts +110 -0
  307. package/src/tui/components/index.ts +79 -0
  308. package/src/tui/components/session/CheckpointPanel.tsx +323 -0
  309. package/src/tui/components/session/RollbackDialog.tsx +169 -0
  310. package/src/tui/components/session/SessionItem.tsx +136 -0
  311. package/src/tui/components/session/SessionListPanel.tsx +252 -0
  312. package/src/tui/components/session/SessionPicker.tsx +449 -0
  313. package/src/tui/components/session/SessionPreview.tsx +240 -0
  314. package/src/tui/components/session/__tests__/session.test.tsx +408 -0
  315. package/src/tui/components/session/index.ts +28 -0
  316. package/src/tui/components/session/types.ts +116 -0
  317. package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
  318. package/src/tui/components/theme/index.ts +227 -0
  319. package/src/tui/components/theme/tokens.ts +484 -0
  320. package/src/tui/config/defaults.ts +134 -0
  321. package/src/tui/config/index.ts +17 -0
  322. package/src/tui/context/AnimationContext.tsx +284 -0
  323. package/src/tui/context/AppContext.tsx +349 -0
  324. package/src/tui/context/BracketedPasteContext.tsx +372 -0
  325. package/src/tui/context/LspContext.tsx +192 -0
  326. package/src/tui/context/McpContext.tsx +325 -0
  327. package/src/tui/context/MessagesContext.tsx +870 -0
  328. package/src/tui/context/OverflowContext.tsx +213 -0
  329. package/src/tui/context/RateLimitContext.tsx +108 -0
  330. package/src/tui/context/ResilienceContext.tsx +275 -0
  331. package/src/tui/context/RootProvider.tsx +136 -0
  332. package/src/tui/context/ScrollContext.tsx +331 -0
  333. package/src/tui/context/ToolsContext.tsx +702 -0
  334. package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
  335. package/src/tui/context/index.ts +140 -0
  336. package/src/tui/enterprise-integration.ts +282 -0
  337. package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
  338. package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
  339. package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
  340. package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
  341. package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
  342. package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
  343. package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
  344. package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
  345. package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
  346. package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
  347. package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
  348. package/src/tui/hooks/index.ts +252 -0
  349. package/src/tui/hooks/useAgentLoop.ts +712 -0
  350. package/src/tui/hooks/useAlternateBuffer.ts +398 -0
  351. package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
  352. package/src/tui/hooks/useBacktrack.ts +443 -0
  353. package/src/tui/hooks/useBracketedPaste.ts +104 -0
  354. package/src/tui/hooks/useCollapsible.ts +240 -0
  355. package/src/tui/hooks/useCopyMode.ts +382 -0
  356. package/src/tui/hooks/useCostSummary.ts +75 -0
  357. package/src/tui/hooks/useDesktopNotification.ts +414 -0
  358. package/src/tui/hooks/useDiffMode.ts +44 -0
  359. package/src/tui/hooks/useFileChangeStats.ts +110 -0
  360. package/src/tui/hooks/useFileSuggestions.ts +284 -0
  361. package/src/tui/hooks/useFlickerDetector.ts +250 -0
  362. package/src/tui/hooks/useGitStatus.ts +200 -0
  363. package/src/tui/hooks/useHotkeys.ts +579 -0
  364. package/src/tui/hooks/useImagePaste.ts +114 -0
  365. package/src/tui/hooks/useInputHighlight.ts +145 -0
  366. package/src/tui/hooks/useInputHistory.ts +246 -0
  367. package/src/tui/hooks/useKeyboardScroll.ts +209 -0
  368. package/src/tui/hooks/useLineBuffer.ts +356 -0
  369. package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
  370. package/src/tui/hooks/useModeController.ts +167 -0
  371. package/src/tui/hooks/useModeShortcuts.ts +196 -0
  372. package/src/tui/hooks/usePermissionHandler.ts +146 -0
  373. package/src/tui/hooks/usePersistence.ts +480 -0
  374. package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
  375. package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
  376. package/src/tui/hooks/useProviderStatus.ts +270 -0
  377. package/src/tui/hooks/useRateLimitStatus.ts +90 -0
  378. package/src/tui/hooks/useScreenReader.ts +315 -0
  379. package/src/tui/hooks/useScrollController.ts +450 -0
  380. package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
  381. package/src/tui/hooks/useSidebarPanelData.ts +115 -0
  382. package/src/tui/hooks/useSmoothScroll.ts +202 -0
  383. package/src/tui/hooks/useSnapshots.ts +300 -0
  384. package/src/tui/hooks/useStateAndRef.ts +50 -0
  385. package/src/tui/hooks/useTerminalSize.ts +206 -0
  386. package/src/tui/hooks/useToolApprovalController.ts +91 -0
  387. package/src/tui/hooks/useVim.ts +334 -0
  388. package/src/tui/hooks/useWorkspace.ts +56 -0
  389. package/src/tui/i18n/__tests__/init.test.ts +278 -0
  390. package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
  391. package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
  392. package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
  393. package/src/tui/i18n/index.ts +72 -0
  394. package/src/tui/i18n/init.ts +131 -0
  395. package/src/tui/i18n/language-config.ts +106 -0
  396. package/src/tui/i18n/locale-detection.ts +173 -0
  397. package/src/tui/i18n/settings-integration.ts +557 -0
  398. package/src/tui/i18n/tui-namespace.ts +538 -0
  399. package/src/tui/i18n/types.ts +312 -0
  400. package/src/tui/index.ts +43 -0
  401. package/src/tui/lsp-integration.ts +409 -0
  402. package/src/tui/metrics-integration.ts +366 -0
  403. package/src/tui/plugins.ts +383 -0
  404. package/src/tui/resilience.ts +342 -0
  405. package/src/tui/sandbox-integration.ts +317 -0
  406. package/src/tui/services/clipboard.ts +348 -0
  407. package/src/tui/services/fuzzy-search.ts +441 -0
  408. package/src/tui/services/index.ts +72 -0
  409. package/src/tui/services/markdown-renderer.ts +565 -0
  410. package/src/tui/services/open-external.ts +247 -0
  411. package/src/tui/services/syntax-highlighter.ts +483 -0
  412. package/src/tui/slash-commands.ts +12 -0
  413. package/src/tui/theme/index.ts +15 -0
  414. package/src/tui/theme/provider.tsx +206 -0
  415. package/src/tui/tip-integration.ts +300 -0
  416. package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
  417. package/src/tui/types/ink-extended.ts +87 -0
  418. package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
  419. package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
  420. package/src/tui/utils/__tests__/text-width.test.ts +158 -0
  421. package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
  422. package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
  423. package/src/tui/utils/bracketedPaste.ts +107 -0
  424. package/src/tui/utils/cursor-manager.ts +131 -0
  425. package/src/tui/utils/detectTerminal.ts +596 -0
  426. package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
  427. package/src/tui/utils/heightEstimator.ts +198 -0
  428. package/src/tui/utils/index.ts +91 -0
  429. package/src/tui/utils/isNarrowWidth.ts +52 -0
  430. package/src/tui/utils/stdoutGuard.ts +90 -0
  431. package/src/tui/utils/synchronized-update.ts +70 -0
  432. package/src/tui/utils/text-width.ts +225 -0
  433. package/src/tui/utils/textSanitizer.ts +225 -0
  434. package/src/tui/utils/textUtils.ts +114 -0
  435. package/src/tui/utils/ui-sizing.ts +192 -0
  436. package/src/tui-blessed/app.ts +160 -0
  437. package/src/tui-blessed/index.ts +2 -0
  438. package/src/tui-blessed/neo-blessed.d.ts +6 -0
  439. package/src/tui-blessed/test.ts +21 -0
  440. package/src/tui-blessed/types.ts +14 -0
  441. package/src/utils/icons.ts +130 -0
  442. package/src/utils/index.ts +33 -0
  443. package/src/utils/resume-hint.ts +86 -0
  444. package/src/version.ts +1 -0
  445. package/tsconfig.json +8 -0
  446. package/vitest.config.ts +35 -0
@@ -0,0 +1,137 @@
1
+ /**
2
+ * MCP Status Panel
3
+ *
4
+ * Minimal, read-only status view for MCP integration.
5
+ * Shows:
6
+ * - connected server count (+ a short list)
7
+ * - registered MCP tool count
8
+ * - last error (if any)
9
+ */
10
+
11
+ import type { ToolRegistry } from "@vellum/core";
12
+ import { Box, Text } from "ink";
13
+ import type React from "react";
14
+ import { useMemo } from "react";
15
+
16
+ import { useMcp } from "../context/McpContext.js";
17
+ import { HotkeyHints } from "./common/HotkeyHints.js";
18
+
19
+ export interface McpPanelProps {
20
+ readonly isFocused: boolean;
21
+ readonly toolRegistry: ToolRegistry;
22
+ }
23
+
24
+ function formatServerStatus(statusInfo: { status: string } | undefined): string {
25
+ if (!statusInfo) {
26
+ return "unknown";
27
+ }
28
+
29
+ // McpServerStatus is a discriminated union; keep display compact.
30
+ switch (statusInfo.status) {
31
+ case "connected":
32
+ return "connected";
33
+ case "connecting":
34
+ return "connecting";
35
+ case "disconnected":
36
+ return "disconnected";
37
+ case "disabled":
38
+ return "disabled";
39
+ case "needs_auth":
40
+ return "needs_auth";
41
+ case "needs_client_registration":
42
+ return "needs_client_registration";
43
+ case "failed":
44
+ return "failed";
45
+ default:
46
+ return statusInfo.status;
47
+ }
48
+ }
49
+
50
+ export function McpPanel({
51
+ isFocused: _isFocused,
52
+ toolRegistry,
53
+ }: McpPanelProps): React.JSX.Element {
54
+ const { hub, isInitialized, isInitializing, error } = useMcp();
55
+
56
+ const connections = hub?.connections ?? [];
57
+
58
+ const mcpTools = toolRegistry.listMcpTools();
59
+ const mcpToolCount = mcpTools.length;
60
+
61
+ const serverLines = useMemo(() => {
62
+ return connections.slice(0, 8).map((connection) => {
63
+ const name = connection.server.name;
64
+ const status = formatServerStatus(connection.server.statusInfo);
65
+
66
+ let suffix = "";
67
+ if (connection.server.statusInfo.status === "failed") {
68
+ suffix = `: ${connection.server.statusInfo.error}`;
69
+ }
70
+ if (connection.server.statusInfo.status === "needs_client_registration") {
71
+ suffix = `: ${connection.server.statusInfo.error}`;
72
+ }
73
+
74
+ return `${name} (${status}${suffix})`;
75
+ });
76
+ }, [connections]);
77
+
78
+ const lastErrorMessage = useMemo(() => {
79
+ if (error) {
80
+ return error.message;
81
+ }
82
+
83
+ for (const c of connections) {
84
+ if (c.server.statusInfo.status === "failed") {
85
+ return c.server.statusInfo.error;
86
+ }
87
+ if (c.server.statusInfo.status === "needs_client_registration") {
88
+ return c.server.statusInfo.error;
89
+ }
90
+ }
91
+
92
+ return null;
93
+ }, [connections, error]);
94
+
95
+ const hints = useMemo(
96
+ () => [
97
+ { keys: "Alt+K", label: "Sidebar" },
98
+ { keys: "Alt+G", label: "Tools" },
99
+ { keys: "Alt+O", label: "MCP" },
100
+ { keys: "Alt+P", label: "Memory" },
101
+ { keys: "Alt+T", label: "Todo" },
102
+ { keys: "Ctrl+S", label: "Sessions" },
103
+ { keys: "Ctrl+Z", label: "Undo" },
104
+ { keys: "Ctrl+Y", label: "Redo" },
105
+ ],
106
+ []
107
+ );
108
+
109
+ return (
110
+ <Box flexDirection="column">
111
+ <Text bold>MCP</Text>
112
+ <Text>Status: {isInitializing ? "initializing" : isInitialized ? "ready" : "not_ready"}</Text>
113
+ <Text>Servers: {connections.length}</Text>
114
+ <Text>MCP tools registered: {mcpToolCount}</Text>
115
+
116
+ {lastErrorMessage ? <Text color="red">Last error: {lastErrorMessage}</Text> : null}
117
+
118
+ {connections.length > 0 ? (
119
+ <Box flexDirection="column" marginTop={1}>
120
+ <Text bold>Servers</Text>
121
+ {serverLines.map((line) => (
122
+ <Text key={line}>- {line}</Text>
123
+ ))}
124
+ {connections.length > serverLines.length ? (
125
+ <Text dimColor>…and {connections.length - serverLines.length} more</Text>
126
+ ) : null}
127
+ </Box>
128
+ ) : (
129
+ <Text dimColor>{isInitializing ? "Connecting…" : "No servers connected."}</Text>
130
+ )}
131
+
132
+ <Box marginTop={1}>
133
+ <HotkeyHints hints={hints} />
134
+ </Box>
135
+ </Box>
136
+ );
137
+ }
@@ -0,0 +1,448 @@
1
+ /**
2
+ * MemoryPanel Component (Phase 31)
3
+ *
4
+ * React Ink component for displaying project memories in a scrollable list.
5
+ * Shows recent memories with expandable details view.
6
+ *
7
+ * @module tui/components/MemoryPanel
8
+ */
9
+
10
+ import type { MemoryEntry, MemoryEntryType } from "@vellum/core";
11
+ import { getIcons } from "@vellum/shared";
12
+ import { Box, Text, useInput } from "ink";
13
+ import type React from "react";
14
+ import { useCallback, useMemo, useState } from "react";
15
+ import { useTUITranslation } from "../i18n/index.js";
16
+ import { useTheme } from "../theme/index.js";
17
+ import { isEndKey, isHomeKey } from "../types/ink-extended.js";
18
+ import { truncateToDisplayWidth } from "../utils/index.js";
19
+ import { HotkeyHints } from "./common/HotkeyHints.js";
20
+
21
+ // =============================================================================
22
+ // Types
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Props for the MemoryPanel component.
27
+ */
28
+ export interface MemoryPanelProps {
29
+ /** List of memory entries to display */
30
+ readonly entries: readonly MemoryEntry[];
31
+ /** Maximum height in lines */
32
+ readonly maxHeight?: number;
33
+ /** Whether the panel is focused for keyboard input */
34
+ readonly isFocused?: boolean;
35
+ /** Callback when an entry is selected */
36
+ readonly onSelectEntry?: (entry: MemoryEntry) => void;
37
+ /** Callback when Enter is pressed on an entry */
38
+ readonly onActivateEntry?: (entry: MemoryEntry) => void;
39
+ /** Whether to show the details panel */
40
+ readonly showDetails?: boolean;
41
+ }
42
+
43
+ // =============================================================================
44
+ // Constants
45
+ // =============================================================================
46
+
47
+ /** Default maximum height */
48
+ const DEFAULT_MAX_HEIGHT = 12;
49
+
50
+ /** Number of items per page */
51
+ const PAGE_SIZE = 5;
52
+
53
+ /**
54
+ * Get type badge colors from theme.
55
+ * Uses semantic theme colors for each memory entry type.
56
+ */
57
+ function getTypeColors(
58
+ theme: ReturnType<typeof useTheme>["theme"]
59
+ ): Record<MemoryEntryType, string> {
60
+ return {
61
+ context: theme.colors.info,
62
+ preference: theme.colors.primary,
63
+ decision: theme.colors.warning,
64
+ summary: theme.colors.success,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Get type icons for memory entry types.
70
+ * Uses centralized icon system with auto-detection.
71
+ */
72
+ function getTypeIcons(): Record<MemoryEntryType, string> {
73
+ const icons = getIcons();
74
+ return {
75
+ context: icons.context,
76
+ preference: icons.preference,
77
+ decision: icons.decision,
78
+ summary: icons.summary,
79
+ };
80
+ }
81
+
82
+ // =============================================================================
83
+ // Helper Functions
84
+ // =============================================================================
85
+
86
+ /**
87
+ * Format a date for display.
88
+ */
89
+ function formatDate(date: Date): string {
90
+ const month = String(date.getMonth() + 1).padStart(2, "0");
91
+ const day = String(date.getDate()).padStart(2, "0");
92
+ const hours = String(date.getHours()).padStart(2, "0");
93
+ const minutes = String(date.getMinutes()).padStart(2, "0");
94
+ return `${month}/${day} ${hours}:${minutes}`;
95
+ }
96
+
97
+ /**
98
+ * Truncate text to a maximum display width.
99
+ * Uses string-width for accurate CJK/Emoji handling.
100
+ */
101
+ function truncate(text: string, maxLength: number): string {
102
+ const singleLine = text.replace(/\n/g, " ").trim();
103
+ return truncateToDisplayWidth(singleLine, maxLength);
104
+ }
105
+
106
+ /**
107
+ * Get visible entries based on scroll position.
108
+ */
109
+ function getVisibleEntries<T>(
110
+ entries: readonly T[],
111
+ scrollOffset: number,
112
+ maxVisible: number
113
+ ): readonly T[] {
114
+ return entries.slice(scrollOffset, scrollOffset + maxVisible);
115
+ }
116
+
117
+ // =============================================================================
118
+ // MemoryItem Component
119
+ // =============================================================================
120
+
121
+ interface MemoryItemProps {
122
+ entry: MemoryEntry;
123
+ isSelected: boolean;
124
+ width: number;
125
+ }
126
+
127
+ function MemoryItem({ entry, isSelected, width }: MemoryItemProps): React.JSX.Element {
128
+ const { theme } = useTheme();
129
+ const typeIcons = getTypeIcons();
130
+ const typeColors = getTypeColors(theme);
131
+ const icon = typeIcons[entry.type];
132
+ const color = typeColors[entry.type];
133
+ const date = formatDate(entry.updatedAt);
134
+
135
+ // Calculate available width for content preview
136
+ const keyWidth = Math.min(entry.key.length, 20);
137
+ const fixedWidth = 4 + keyWidth + 2 + 12; // icon + key + spacing + date
138
+ const contentWidth = Math.max(width - fixedWidth - 4, 10);
139
+ const preview = truncate(entry.content, contentWidth);
140
+
141
+ return (
142
+ <Box>
143
+ {isSelected && (
144
+ <Text color={theme.colors.primary} bold>
145
+ {"› "}
146
+ </Text>
147
+ )}
148
+ {!isSelected && <Text dimColor>{" "}</Text>}
149
+
150
+ <Text>{icon} </Text>
151
+ <Text color={color} bold>
152
+ {truncate(entry.key, 20)}
153
+ </Text>
154
+ <Text dimColor> </Text>
155
+ <Text color={theme.colors.muted}>{preview}</Text>
156
+ <Text dimColor> </Text>
157
+ <Text dimColor>{date}</Text>
158
+ </Box>
159
+ );
160
+ }
161
+
162
+ // =============================================================================
163
+ // MemoryDetails Component
164
+ // =============================================================================
165
+
166
+ interface MemoryDetailsProps {
167
+ entry: MemoryEntry;
168
+ width: number;
169
+ }
170
+
171
+ function MemoryDetails({ entry, width }: MemoryDetailsProps): React.JSX.Element {
172
+ const { theme } = useTheme();
173
+ const { t } = useTUITranslation();
174
+ const typeIcons = getTypeIcons();
175
+ const typeColors = getTypeColors(theme);
176
+ const icon = typeIcons[entry.type];
177
+ const color = typeColors[entry.type];
178
+
179
+ // Wrap content to width
180
+ const contentLines = entry.content.split("\n").slice(0, 5);
181
+ const hasMore = entry.content.split("\n").length > 5;
182
+
183
+ return (
184
+ <Box
185
+ flexDirection="column"
186
+ borderStyle="single"
187
+ borderColor={theme.semantic.border.default}
188
+ paddingX={1}
189
+ >
190
+ <Box marginBottom={1}>
191
+ <Text>{icon} </Text>
192
+ <Text color={color} bold>
193
+ {entry.key}
194
+ </Text>
195
+ <Text dimColor> ({entry.type})</Text>
196
+ </Box>
197
+
198
+ <Box flexDirection="column" marginLeft={1}>
199
+ {contentLines.map((line, i) => (
200
+ <Text key={`${entry.key}-line-${i}`} wrap="truncate">
201
+ {truncate(line, width - 6)}
202
+ </Text>
203
+ ))}
204
+ {hasMore && <Text dimColor>…</Text>}
205
+ </Box>
206
+
207
+ <Box marginTop={1} flexDirection="column">
208
+ <Text dimColor>─────────────────</Text>
209
+ <Box>
210
+ <Text color={theme.colors.muted}>{t("memory.created")}</Text>
211
+ <Text>{formatDate(entry.createdAt)}</Text>
212
+ </Box>
213
+ <Box>
214
+ <Text color={theme.colors.muted}>{t("memory.updated")}</Text>
215
+ <Text>{formatDate(entry.updatedAt)}</Text>
216
+ </Box>
217
+ {entry.metadata.tags.length > 0 && (
218
+ <Box>
219
+ <Text color={theme.colors.muted}>{t("memory.tags")}</Text>
220
+ <Text color="cyan">{entry.metadata.tags.map((t) => `#${t}`).join(" ")}</Text>
221
+ </Box>
222
+ )}
223
+ <Box>
224
+ <Text color={theme.colors.muted}>{t("memory.importance")}</Text>
225
+ <Text>{(entry.metadata.importance * 100).toFixed(0)}%</Text>
226
+ </Box>
227
+ </Box>
228
+ </Box>
229
+ );
230
+ }
231
+
232
+ // =============================================================================
233
+ // Main MemoryPanel Component
234
+ // =============================================================================
235
+
236
+ /**
237
+ * MemoryPanel - Displays project memories in a scrollable list.
238
+ *
239
+ * Features:
240
+ * - j/k or arrow keys for navigation
241
+ * - Page up/down support
242
+ * - Enter to activate an entry
243
+ * - Visual scroll indicators
244
+ * - Optional details panel
245
+ *
246
+ * @example
247
+ * ```tsx
248
+ * <MemoryPanel
249
+ * entries={memoryEntries}
250
+ * maxHeight={10}
251
+ * isFocused={true}
252
+ * showDetails={true}
253
+ * onSelectEntry={(entry) => console.log(entry.key)}
254
+ * />
255
+ * ```
256
+ */
257
+ export function MemoryPanel({
258
+ entries,
259
+ maxHeight = DEFAULT_MAX_HEIGHT,
260
+ isFocused = true,
261
+ onSelectEntry,
262
+ onActivateEntry,
263
+ showDetails = false,
264
+ }: MemoryPanelProps): React.JSX.Element {
265
+ const { theme } = useTheme();
266
+ const { t } = useTUITranslation();
267
+
268
+ const hints = useMemo(
269
+ () => [
270
+ { keys: "Alt+K", label: "Sidebar" },
271
+ { keys: "Alt+G", label: "Tools" },
272
+ { keys: "Alt+O", label: "MCP" },
273
+ { keys: "Alt+P", label: "Memory" },
274
+ { keys: "Alt+T", label: "Todo" },
275
+ { keys: "Ctrl+S", label: "Sessions" },
276
+ { keys: "Ctrl+Z", label: "Undo" },
277
+ { keys: "Ctrl+Y", label: "Redo" },
278
+ ],
279
+ []
280
+ );
281
+
282
+ // Track selection and scroll
283
+ const [selectedIndex, setSelectedIndex] = useState(0);
284
+ const [scrollOffset, setScrollOffset] = useState(0);
285
+
286
+ // Calculate visible items (account for header)
287
+ const maxVisible = Math.max(1, maxHeight - 3);
288
+
289
+ // Get visible entries
290
+ const visibleEntries = useMemo(
291
+ () => getVisibleEntries(entries, scrollOffset, maxVisible),
292
+ [entries, scrollOffset, maxVisible]
293
+ );
294
+
295
+ // Get selected entry
296
+ const selectedEntry = entries[selectedIndex];
297
+
298
+ // Scroll indicators
299
+ const canScrollUp = scrollOffset > 0;
300
+ const canScrollDown = scrollOffset + maxVisible < entries.length;
301
+
302
+ /**
303
+ * Navigate to a specific index.
304
+ */
305
+ const navigateToIndex = useCallback(
306
+ (newIndex: number) => {
307
+ const clampedIndex = Math.max(0, Math.min(newIndex, entries.length - 1));
308
+ setSelectedIndex(clampedIndex);
309
+
310
+ // Adjust scroll to keep selection visible
311
+ if (clampedIndex < scrollOffset) {
312
+ setScrollOffset(clampedIndex);
313
+ } else if (clampedIndex >= scrollOffset + maxVisible) {
314
+ setScrollOffset(clampedIndex - maxVisible + 1);
315
+ }
316
+
317
+ // Notify parent
318
+ if (onSelectEntry && entries[clampedIndex]) {
319
+ onSelectEntry(entries[clampedIndex]);
320
+ }
321
+ },
322
+ [entries, scrollOffset, maxVisible, onSelectEntry]
323
+ );
324
+
325
+ // Handle keyboard input
326
+ useInput(
327
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Input handler must process multiple key bindings for navigation and actions
328
+ (input, key) => {
329
+ if (!isFocused || entries.length === 0) return;
330
+
331
+ // Navigation down
332
+ if (input === "j" || key.downArrow) {
333
+ navigateToIndex(selectedIndex + 1);
334
+ return;
335
+ }
336
+
337
+ // Navigation up
338
+ if (input === "k" || key.upArrow) {
339
+ navigateToIndex(selectedIndex - 1);
340
+ return;
341
+ }
342
+
343
+ // Page down
344
+ if (key.pageDown || (key.ctrl && input === "d")) {
345
+ navigateToIndex(selectedIndex + PAGE_SIZE);
346
+ return;
347
+ }
348
+
349
+ // Page up
350
+ if (key.pageUp || (key.ctrl && input === "u")) {
351
+ navigateToIndex(selectedIndex - PAGE_SIZE);
352
+ return;
353
+ }
354
+
355
+ // Home
356
+ if (input === "g" || isHomeKey(input)) {
357
+ navigateToIndex(0);
358
+ return;
359
+ }
360
+
361
+ // End
362
+ if (input === "G" || isEndKey(input)) {
363
+ navigateToIndex(entries.length - 1);
364
+ return;
365
+ }
366
+
367
+ // Enter to activate
368
+ if (key.return && selectedEntry && onActivateEntry) {
369
+ onActivateEntry(selectedEntry);
370
+ }
371
+ },
372
+ { isActive: isFocused }
373
+ );
374
+
375
+ // Empty state
376
+ if (entries.length === 0) {
377
+ return (
378
+ <Box flexDirection="column" paddingX={1}>
379
+ <Box marginBottom={1}>
380
+ <Text color={theme.colors.primary} bold>
381
+ {t("memory.title")}
382
+ </Text>
383
+ </Box>
384
+ <Text dimColor>{t("memory.empty")}</Text>
385
+ <Text dimColor>{t("memory.emptyHint")}</Text>
386
+ </Box>
387
+ );
388
+ }
389
+
390
+ // Estimate terminal width (fallback)
391
+ const estimatedWidth = 80;
392
+
393
+ return (
394
+ <Box flexDirection="column">
395
+ {/* Header */}
396
+ <Box marginBottom={1} paddingX={1}>
397
+ <Text color={theme.colors.primary} bold>
398
+ {t("memory.title")}
399
+ </Text>
400
+ <Text dimColor>
401
+ {" "}
402
+ ({entries.length} {entries.length === 1 ? t("memory.entry") : t("memory.entries")})
403
+ </Text>
404
+ </Box>
405
+
406
+ {/* Scroll indicator - up */}
407
+ {canScrollUp && (
408
+ <Box paddingX={1}>
409
+ <Text dimColor>↑ {scrollOffset} more above</Text>
410
+ </Box>
411
+ )}
412
+
413
+ {/* Memory list */}
414
+ <Box flexDirection="column" paddingX={1}>
415
+ {visibleEntries.map((entry, i) => (
416
+ <MemoryItem
417
+ key={entry.key}
418
+ entry={entry}
419
+ isSelected={scrollOffset + i === selectedIndex}
420
+ width={estimatedWidth}
421
+ />
422
+ ))}
423
+ </Box>
424
+
425
+ {/* Scroll indicator - down */}
426
+ {canScrollDown && (
427
+ <Box paddingX={1}>
428
+ <Text dimColor>↓ {entries.length - scrollOffset - maxVisible} more below</Text>
429
+ </Box>
430
+ )}
431
+
432
+ {/* Details panel */}
433
+ {showDetails && selectedEntry && (
434
+ <Box marginTop={1} paddingX={1}>
435
+ <MemoryDetails entry={selectedEntry} width={estimatedWidth} />
436
+ </Box>
437
+ )}
438
+
439
+ {/* Help hint */}
440
+ {isFocused && (
441
+ <Box marginTop={1} paddingX={1} flexDirection="column">
442
+ <Text dimColor>{t("memory.keybindings")}</Text>
443
+ <HotkeyHints hints={hints} />
444
+ </Box>
445
+ )}
446
+ </Box>
447
+ );
448
+ }