@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,268 @@
1
+ /**
2
+ * MessageBubble Component Tests
3
+ *
4
+ * Tests for the MessageBubble component which renders styled message bubbles
5
+ * with role-specific formatting and alignment.
6
+ *
7
+ * @module tui/components/Messages/__tests__/MessageBubble.test
8
+ */
9
+
10
+ import { getIcons } from "@vellum/shared";
11
+ import { render } from "ink-testing-library";
12
+ import type React from "react";
13
+ import { describe, expect, it } from "vitest";
14
+ import type { Message } from "../../../context/MessagesContext.js";
15
+ import { ThemeProvider } from "../../../theme/index.js";
16
+ import { MessageBubble } from "../MessageBubble.js";
17
+
18
+ // Get icons for test assertions
19
+ const icons = getIcons();
20
+
21
+ // =============================================================================
22
+ // Test Helpers
23
+ // =============================================================================
24
+
25
+ /**
26
+ * Wrap component with ThemeProvider for testing.
27
+ */
28
+ function renderWithTheme(ui: React.ReactElement) {
29
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
30
+ }
31
+
32
+ /**
33
+ * Create a test message with default values.
34
+ */
35
+ function createTestMessage(overrides: Partial<Message> = {}): Message {
36
+ return {
37
+ id: "test-1",
38
+ role: "user",
39
+ content: "Test message content",
40
+ timestamp: new Date("2025-01-01T12:00:00Z"),
41
+ ...overrides,
42
+ };
43
+ }
44
+
45
+ // =============================================================================
46
+ // Tests
47
+ // =============================================================================
48
+
49
+ describe("MessageBubble", () => {
50
+ describe("rendering", () => {
51
+ it("renders user message content", () => {
52
+ const message = createTestMessage({
53
+ role: "user",
54
+ content: "Hello from user",
55
+ });
56
+
57
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
58
+
59
+ expect(lastFrame()).toContain("Hello from user");
60
+ expect(lastFrame()).toContain("You");
61
+ });
62
+
63
+ it("renders assistant message content", () => {
64
+ const message = createTestMessage({
65
+ role: "assistant",
66
+ content: "Hello from assistant",
67
+ });
68
+
69
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
70
+
71
+ expect(lastFrame()).toContain("Hello from assistant");
72
+ expect(lastFrame()).toContain("Vellum");
73
+ });
74
+
75
+ it("renders system message content", () => {
76
+ const message = createTestMessage({
77
+ role: "system",
78
+ content: "System notification",
79
+ });
80
+
81
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
82
+
83
+ expect(lastFrame()).toContain("System notification");
84
+ expect(lastFrame()).toContain("System");
85
+ });
86
+
87
+ it("renders tool message content", () => {
88
+ const message = createTestMessage({
89
+ role: "tool",
90
+ content: "Tool output",
91
+ });
92
+
93
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
94
+
95
+ expect(lastFrame()).toContain("Tool output");
96
+ expect(lastFrame()).toContain("Tool");
97
+ });
98
+
99
+ it("renders empty message placeholder", () => {
100
+ const message = createTestMessage({
101
+ content: "",
102
+ });
103
+
104
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
105
+
106
+ expect(lastFrame()).toContain("(empty)");
107
+ });
108
+
109
+ it("renders streaming indicator", () => {
110
+ const message = createTestMessage({
111
+ content: "",
112
+ isStreaming: true,
113
+ });
114
+
115
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
116
+
117
+ expect(lastFrame()).toContain("streaming");
118
+ expect(lastFrame()).toContain("...");
119
+ });
120
+ });
121
+
122
+ describe("timestamp display", () => {
123
+ it("does not show timestamp by default", () => {
124
+ const message = createTestMessage();
125
+
126
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
127
+
128
+ // Should not contain time format like "12:00"
129
+ expect(lastFrame()).not.toContain("•");
130
+ });
131
+
132
+ it("shows timestamp when showTimestamp is true", () => {
133
+ const message = createTestMessage({
134
+ timestamp: new Date("2025-01-01T14:30:00Z"),
135
+ });
136
+
137
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} showTimestamp />);
138
+
139
+ expect(lastFrame()).toContain("•");
140
+ });
141
+ });
142
+
143
+ describe("avatar display", () => {
144
+ it("does not show avatar by default", () => {
145
+ const message = createTestMessage({ role: "user" });
146
+
147
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
148
+
149
+ // User icon should not appear
150
+ expect(lastFrame()).not.toContain(icons.user);
151
+ });
152
+
153
+ it("shows user avatar when showAvatar is true", () => {
154
+ const message = createTestMessage({ role: "user" });
155
+
156
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
157
+
158
+ expect(lastFrame()).toContain(icons.user);
159
+ });
160
+
161
+ it("shows assistant avatar when showAvatar is true", () => {
162
+ const message = createTestMessage({ role: "assistant" });
163
+
164
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
165
+
166
+ expect(lastFrame()).toContain(icons.assistant);
167
+ });
168
+
169
+ it("shows system avatar when showAvatar is true", () => {
170
+ const message = createTestMessage({ role: "system" });
171
+
172
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
173
+
174
+ expect(lastFrame()).toContain(icons.system);
175
+ });
176
+
177
+ it("shows tool avatar when showAvatar is true", () => {
178
+ const message = createTestMessage({ role: "tool" });
179
+
180
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} showAvatar />);
181
+
182
+ expect(lastFrame()).toContain(icons.tool);
183
+ });
184
+ });
185
+
186
+ describe("compact mode", () => {
187
+ it("renders in compact mode", () => {
188
+ const message = createTestMessage({
189
+ content: "Compact message",
190
+ });
191
+
192
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} compact />);
193
+
194
+ expect(lastFrame()).toContain("Compact message");
195
+ });
196
+
197
+ it("renders assistant message in compact mode without extra spacing", () => {
198
+ const message = createTestMessage({
199
+ role: "assistant",
200
+ content: "Compact assistant message",
201
+ });
202
+
203
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} compact />);
204
+
205
+ expect(lastFrame()).toContain("Compact assistant message");
206
+ expect(lastFrame()).toContain("Vellum");
207
+ });
208
+ });
209
+
210
+ describe("unknown/unhandled roles", () => {
211
+ it("renders unknown role with fallback label", () => {
212
+ // tool_group role is not handled by MessageBubble (use ToolGroupItem instead)
213
+ // This tests the fallback behavior for unrecognized roles
214
+ const message = createTestMessage({
215
+ role: "tool_group", // Not handled by MessageBubble
216
+ content: "Some content",
217
+ });
218
+
219
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
220
+
221
+ // Shows "Unknown" label for unhandled roles
222
+ expect(lastFrame()).toContain("Unknown");
223
+ expect(lastFrame()).toContain("Some content");
224
+ });
225
+
226
+ it("renders unknown role with empty content placeholder", () => {
227
+ const message = createTestMessage({
228
+ role: "tool_group",
229
+ content: "",
230
+ });
231
+
232
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
233
+
234
+ // Empty content shows placeholder
235
+ expect(lastFrame()).toContain("(empty)");
236
+ });
237
+ });
238
+
239
+ describe("combined props", () => {
240
+ it("renders with all props enabled", () => {
241
+ const message = createTestMessage({
242
+ role: "assistant",
243
+ content: "Full featured message",
244
+ });
245
+
246
+ const { lastFrame } = renderWithTheme(
247
+ <MessageBubble message={message} showTimestamp showAvatar />
248
+ );
249
+
250
+ expect(lastFrame()).toContain("Full featured message");
251
+ expect(lastFrame()).toContain(icons.assistant); // Avatar
252
+ expect(lastFrame()).toContain("Vellum");
253
+ expect(lastFrame()).toContain("•"); // Timestamp separator
254
+ });
255
+
256
+ it("renders message with thinking content", () => {
257
+ const message = createTestMessage({
258
+ role: "assistant",
259
+ content: "Final response",
260
+ thinking: "Let me think about this...",
261
+ });
262
+
263
+ const { lastFrame } = renderWithTheme(<MessageBubble message={message} />);
264
+
265
+ expect(lastFrame()).toContain("Final response");
266
+ });
267
+ });
268
+ });
@@ -0,0 +1,324 @@
1
+ /**
2
+ * MessageList Component Tests (T017)
3
+ *
4
+ * Tests for the MessageList component with auto-scroll functionality.
5
+ */
6
+
7
+ import { getIcons } from "@vellum/shared";
8
+ import { render } from "ink-testing-library";
9
+ import type React from "react";
10
+ import { describe, expect, it } from "vitest";
11
+ import type { Message } from "../../../context/MessagesContext.js";
12
+ import { ThemeProvider } from "../../../theme/index.js";
13
+ import { MessageList } from "../MessageList.js";
14
+
15
+ // Get icons for test assertions
16
+ const icons = getIcons();
17
+
18
+ /**
19
+ * Wrapper to provide theme context for tests
20
+ */
21
+ function renderWithTheme(element: React.ReactElement) {
22
+ return render(<ThemeProvider>{element}</ThemeProvider>);
23
+ }
24
+
25
+ /**
26
+ * Create a test message with defaults
27
+ */
28
+ function createMessage(overrides: Partial<Message> = {}): Message {
29
+ return {
30
+ id: `msg-${Math.random().toString(36).slice(2)}`,
31
+ role: "user",
32
+ content: "Test message content",
33
+ timestamp: new Date(),
34
+ ...overrides,
35
+ };
36
+ }
37
+
38
+ describe("MessageList", () => {
39
+ describe("Rendering", () => {
40
+ it("should render without crashing with empty messages", () => {
41
+ const { lastFrame } = renderWithTheme(<MessageList messages={[]} />);
42
+
43
+ expect(lastFrame()).toBeDefined();
44
+ expect(lastFrame()).toContain("No messages yet");
45
+ });
46
+
47
+ it("should render a single message", () => {
48
+ const messages = [createMessage({ content: "Hello, world!" })];
49
+
50
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
51
+
52
+ const frame = lastFrame() ?? "";
53
+ expect(frame).toContain("Hello, world!");
54
+ expect(frame).toContain("You"); // User role label
55
+ expect(frame).toContain(icons.user); // User role icon
56
+ });
57
+
58
+ it("should render multiple messages in order", () => {
59
+ const messages = [
60
+ createMessage({ id: "1", content: "First message", role: "user" }),
61
+ createMessage({ id: "2", content: "Second message", role: "assistant" }),
62
+ createMessage({ id: "3", content: "Third message", role: "user" }),
63
+ ];
64
+
65
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
66
+
67
+ const frame = lastFrame() ?? "";
68
+ expect(frame).toContain("First message");
69
+ expect(frame).toContain("Second message");
70
+ expect(frame).toContain("Third message");
71
+ });
72
+
73
+ it("should render different role icons correctly", () => {
74
+ const messages = [
75
+ createMessage({ role: "user", content: "User msg" }),
76
+ createMessage({ role: "assistant", content: "Assistant msg" }),
77
+ createMessage({ role: "system", content: "System msg" }),
78
+ createMessage({ role: "tool", content: "Tool msg" }),
79
+ ];
80
+
81
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
82
+
83
+ const frame = lastFrame() ?? "";
84
+ expect(frame).toContain(icons.user); // user
85
+ expect(frame).toContain(icons.assistant); // assistant
86
+ expect(frame).toContain(icons.system); // system
87
+ expect(frame).toContain(icons.tool); // tool
88
+ });
89
+
90
+ it("should render role labels correctly", () => {
91
+ const messages = [
92
+ createMessage({ role: "user" }),
93
+ createMessage({ role: "assistant" }),
94
+ createMessage({ role: "system" }),
95
+ createMessage({ role: "tool" }),
96
+ ];
97
+
98
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
99
+
100
+ const frame = lastFrame() ?? "";
101
+ expect(frame).toContain("You");
102
+ expect(frame).toContain("Vellum");
103
+ expect(frame).toContain("System");
104
+ expect(frame).toContain("Tool");
105
+ });
106
+
107
+ it("should show streaming indicator when message is streaming", () => {
108
+ const messages = [createMessage({ content: "Typing...", isStreaming: true })];
109
+
110
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
111
+
112
+ const frame = lastFrame() ?? "";
113
+ expect(frame).toContain("streaming");
114
+ });
115
+
116
+ it("should show placeholder for empty content in non-streaming message", () => {
117
+ const messages = [createMessage({ content: "", isStreaming: false })];
118
+
119
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
120
+
121
+ const frame = lastFrame() ?? "";
122
+ expect(frame).toContain("(empty)");
123
+ });
124
+
125
+ it("should show ellipsis for empty content in streaming message", () => {
126
+ const messages = [createMessage({ content: "", isStreaming: true })];
127
+
128
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
129
+
130
+ const frame = lastFrame() ?? "";
131
+ expect(frame).toContain("...");
132
+ });
133
+ });
134
+
135
+ describe("Tool Group Messages", () => {
136
+ it("should render tool_group message as inline tool rows", () => {
137
+ const messages = [
138
+ createMessage({
139
+ role: "assistant",
140
+ content: "Let me read that file",
141
+ }),
142
+ createMessage({
143
+ id: "tool-group-1",
144
+ role: "tool_group",
145
+ content: "",
146
+ toolCalls: [
147
+ {
148
+ id: "tc-1",
149
+ name: "read_file",
150
+ arguments: { path: "/test.txt" },
151
+ status: "completed",
152
+ },
153
+ ],
154
+ }),
155
+ ];
156
+
157
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
158
+
159
+ const frame = lastFrame() ?? "";
160
+ expect(frame).toContain("Let me read that file");
161
+ expect(frame).toContain("read_file");
162
+ });
163
+
164
+ it("should render tool_group when multiple tool calls present", () => {
165
+ const messages = [
166
+ createMessage({
167
+ role: "assistant",
168
+ content: "Processing multiple files",
169
+ }),
170
+ createMessage({
171
+ id: "tool-group-1",
172
+ role: "tool_group",
173
+ content: "",
174
+ toolCalls: [
175
+ {
176
+ id: "tc-1",
177
+ name: "read_file",
178
+ arguments: {},
179
+ status: "completed",
180
+ },
181
+ {
182
+ id: "tc-2",
183
+ name: "write_file",
184
+ arguments: {},
185
+ status: "running",
186
+ },
187
+ ],
188
+ }),
189
+ ];
190
+
191
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
192
+
193
+ const frame = lastFrame() ?? "";
194
+ expect(frame).toContain("Processing multiple files");
195
+ expect(frame).toContain("read_file");
196
+ expect(frame).toContain("write_file");
197
+ });
198
+
199
+ it("should not render a Tools header for tool_group rows", () => {
200
+ const messages = [
201
+ createMessage({
202
+ id: "tool-group-1",
203
+ role: "tool_group",
204
+ content: "",
205
+ toolCalls: [
206
+ {
207
+ id: "tc-1",
208
+ name: "bash",
209
+ arguments: { command: "ls" },
210
+ status: "pending",
211
+ },
212
+ ],
213
+ }),
214
+ ];
215
+
216
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
217
+
218
+ const frame = lastFrame() ?? "";
219
+ expect(frame).toContain("bash");
220
+ expect(frame).not.toContain("Tools");
221
+ });
222
+ });
223
+
224
+ describe("Auto-Scroll Behavior", () => {
225
+ it("should have autoScroll enabled by default", () => {
226
+ const messages = [createMessage()];
227
+
228
+ // Just verify the component renders with autoScroll defaulting to true
229
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
230
+
231
+ // If autoScroll was false and we scrolled up, we'd see the pause indicator
232
+ // Since autoScroll defaults to true and we haven't scrolled, no indicator
233
+ const frame = lastFrame() ?? "";
234
+ expect(frame).not.toContain("Auto-scroll paused");
235
+ });
236
+
237
+ it("should report being at bottom when all messages are visible", () => {
238
+ const messages = [createMessage(), createMessage(), createMessage()];
239
+
240
+ // Without maxHeight, all messages are visible, so we're always "at bottom"
241
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
242
+
243
+ // Should render without scroll indicators since no maxHeight
244
+ const frame = lastFrame() ?? "";
245
+ expect(frame).not.toContain("more above");
246
+ expect(frame).not.toContain("more below");
247
+ });
248
+
249
+ it("should show scroll indicator when messages exceed maxHeight", () => {
250
+ // Create more messages than maxHeight
251
+ const messages = Array.from({ length: 10 }, (_, i) =>
252
+ createMessage({ id: `msg-${i}`, content: `Message ${i}` })
253
+ );
254
+
255
+ const { lastFrame } = renderWithTheme(
256
+ <MessageList messages={messages} maxHeight={3} autoScroll={false} />
257
+ );
258
+
259
+ const frame = lastFrame() ?? "";
260
+ // Should show scroll down indicator since we're at top (autoScroll=false)
261
+ // The component should show some messages but not all
262
+ expect(frame).toBeDefined();
263
+ });
264
+
265
+ it("should accept autoScroll prop set to false", () => {
266
+ const messages = [createMessage()];
267
+
268
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} autoScroll={false} />);
269
+
270
+ expect(lastFrame()).toBeDefined();
271
+ });
272
+ });
273
+
274
+ describe("Props Validation", () => {
275
+ it("should handle undefined optional props", () => {
276
+ const messages = [createMessage()];
277
+
278
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
279
+
280
+ expect(lastFrame()).toBeDefined();
281
+ });
282
+
283
+ it("should handle maxHeight of 0", () => {
284
+ const messages = [createMessage()];
285
+
286
+ // maxHeight of 0 should be treated as "no max height"
287
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} maxHeight={0} />);
288
+
289
+ expect(lastFrame()).toBeDefined();
290
+ });
291
+
292
+ it("should handle empty message content", () => {
293
+ const messages = [createMessage({ content: "" })];
294
+
295
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
296
+
297
+ expect(lastFrame()).toBeDefined();
298
+ });
299
+ });
300
+
301
+ describe("Performance", () => {
302
+ it("should render many messages without error", () => {
303
+ const messages = Array.from({ length: 100 }, (_, i) =>
304
+ createMessage({ id: `msg-${i}`, content: `Message number ${i}` })
305
+ );
306
+
307
+ const { lastFrame } = renderWithTheme(<MessageList messages={messages} />);
308
+
309
+ expect(lastFrame()).toBeDefined();
310
+ });
311
+
312
+ it("should handle messages with windowed rendering", () => {
313
+ const messages = Array.from({ length: 50 }, (_, i) =>
314
+ createMessage({ id: `msg-${i}`, content: `Message ${i}` })
315
+ );
316
+
317
+ const { lastFrame } = renderWithTheme(
318
+ <MessageList messages={messages} maxHeight={10} autoScroll={true} />
319
+ );
320
+
321
+ expect(lastFrame()).toBeDefined();
322
+ });
323
+ });
324
+ });