@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,239 @@
1
+ /**
2
+ * DiffView Component Tests (T022)
3
+ *
4
+ * @module tui/components/Messages/__tests__/DiffView.test
5
+ */
6
+
7
+ import { render } from "ink-testing-library";
8
+ import { describe, expect, it } from "vitest";
9
+ import { ThemeProvider } from "../../../theme/index.js";
10
+ import { DiffView } from "../DiffView.js";
11
+
12
+ /**
13
+ * Wrap component with ThemeProvider for testing.
14
+ */
15
+ function renderWithTheme(ui: React.ReactElement) {
16
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
17
+ }
18
+
19
+ // Sample unified diff for testing
20
+ const sampleDiff = `--- a/src/index.ts
21
+ +++ b/src/index.ts
22
+ @@ -1,5 +1,6 @@
23
+ import { foo } from './foo';
24
+ +import { bar } from './bar';
25
+
26
+ function main() {
27
+ - console.log('hello');
28
+ + console.log('hello world');
29
+ }`;
30
+
31
+ const simpleAddition = `@@ -1,3 +1,4 @@
32
+ line 1
33
+ +new line
34
+ line 2
35
+ line 3`;
36
+
37
+ const simpleDeletion = `@@ -1,4 +1,3 @@
38
+ line 1
39
+ -deleted line
40
+ line 2
41
+ line 3`;
42
+
43
+ describe("DiffView", () => {
44
+ describe("basic rendering", () => {
45
+ it("renders diff content", () => {
46
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} />);
47
+
48
+ expect(lastFrame()).toContain("foo");
49
+ expect(lastFrame()).toContain("main");
50
+ });
51
+
52
+ it("renders empty diff", () => {
53
+ const { lastFrame } = renderWithTheme(<DiffView diff="" />);
54
+
55
+ // Should render borders at minimum
56
+ expect(lastFrame()).toBeTruthy();
57
+ });
58
+
59
+ it("renders with borders", () => {
60
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} />);
61
+
62
+ // Check for box-drawing characters (borders)
63
+ expect(lastFrame()).toMatch(/[─│┌┐└┘┬┴├┤┼╭╮╯╰]/);
64
+ });
65
+ });
66
+
67
+ describe("file header", () => {
68
+ it("displays custom fileName when provided", () => {
69
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} fileName="src/index.ts" />);
70
+
71
+ expect(lastFrame()).toContain("src/index.ts");
72
+ });
73
+
74
+ it("shows file icon with fileName", () => {
75
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} fileName="test.ts" />);
76
+
77
+ expect(lastFrame()).toContain("📄");
78
+ });
79
+
80
+ it("hides built-in diff headers when fileName provided", () => {
81
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} fileName="custom.ts" />);
82
+
83
+ // Should not show the original --- and +++ headers
84
+ expect(lastFrame()).not.toContain("---");
85
+ expect(lastFrame()).not.toContain("+++");
86
+ });
87
+ });
88
+
89
+ describe("added lines", () => {
90
+ it("displays + prefix for added lines", () => {
91
+ const { lastFrame } = renderWithTheme(<DiffView diff={simpleAddition} />);
92
+
93
+ expect(lastFrame()).toContain("+");
94
+ expect(lastFrame()).toContain("new line");
95
+ });
96
+
97
+ it("renders added line content correctly", () => {
98
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} />);
99
+
100
+ expect(lastFrame()).toContain("bar");
101
+ expect(lastFrame()).toContain("hello world");
102
+ });
103
+ });
104
+
105
+ describe("removed lines", () => {
106
+ it("displays - prefix for removed lines", () => {
107
+ const { lastFrame } = renderWithTheme(<DiffView diff={simpleDeletion} />);
108
+
109
+ expect(lastFrame()).toContain("-");
110
+ expect(lastFrame()).toContain("deleted line");
111
+ });
112
+
113
+ it("renders removed line content correctly", () => {
114
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} />);
115
+
116
+ // The original line before modification
117
+ expect(lastFrame()).toContain("hello");
118
+ });
119
+ });
120
+
121
+ describe("context lines", () => {
122
+ it("renders unchanged context lines", () => {
123
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} />);
124
+
125
+ expect(lastFrame()).toContain("import { foo }");
126
+ expect(lastFrame()).toContain("function main");
127
+ });
128
+ });
129
+
130
+ describe("hunk headers", () => {
131
+ it("renders hunk headers", () => {
132
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} />);
133
+
134
+ expect(lastFrame()).toContain("@@");
135
+ });
136
+ });
137
+
138
+ describe("line numbers", () => {
139
+ it("shows line numbers when enabled", () => {
140
+ const { lastFrame } = renderWithTheme(<DiffView diff={simpleAddition} showLineNumbers />);
141
+
142
+ // Should show line numbers
143
+ expect(lastFrame()).toContain("1");
144
+ expect(lastFrame()).toContain("2");
145
+ });
146
+
147
+ it("hides line numbers when disabled", () => {
148
+ const { lastFrame } = renderWithTheme(
149
+ <DiffView diff={simpleAddition} showLineNumbers={false} />
150
+ );
151
+
152
+ // Line numbers separator pattern should not appear in diff content area
153
+ // Context lines should just have the content
154
+ expect(lastFrame()).toContain("line 1");
155
+ });
156
+ });
157
+
158
+ describe("compact mode", () => {
159
+ it("renders in compact mode without errors", () => {
160
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} compact />);
161
+
162
+ expect(lastFrame()).toContain("foo");
163
+ });
164
+
165
+ it("reduces padding in compact mode", () => {
166
+ const normalRender = renderWithTheme(<DiffView diff={sampleDiff} />);
167
+ const compactRender = renderWithTheme(<DiffView diff={sampleDiff} compact />);
168
+
169
+ // Both should render successfully
170
+ expect(normalRender.lastFrame()).toBeTruthy();
171
+ expect(compactRender.lastFrame()).toBeTruthy();
172
+ });
173
+ });
174
+
175
+ describe("unified diff format parsing", () => {
176
+ it("parses file headers correctly", () => {
177
+ const { lastFrame } = renderWithTheme(<DiffView diff={sampleDiff} />);
178
+
179
+ // Without fileName prop, should show the original headers
180
+ expect(lastFrame()).toContain("---");
181
+ expect(lastFrame()).toContain("+++");
182
+ });
183
+
184
+ it("handles multiple hunks", () => {
185
+ const multiHunkDiff = `@@ -1,3 +1,4 @@
186
+ first
187
+ +added first
188
+ second
189
+ @@ -10,3 +11,4 @@
190
+ tenth
191
+ +added tenth
192
+ eleventh`;
193
+
194
+ const { lastFrame } = renderWithTheme(<DiffView diff={multiHunkDiff} />);
195
+
196
+ expect(lastFrame()).toContain("first");
197
+ expect(lastFrame()).toContain("tenth");
198
+ expect(lastFrame()).toContain("added first");
199
+ expect(lastFrame()).toContain("added tenth");
200
+ });
201
+
202
+ it("handles diff with only additions", () => {
203
+ const additionsOnly = `@@ -0,0 +1,3 @@
204
+ +new file
205
+ +line 2
206
+ +line 3`;
207
+
208
+ const { lastFrame } = renderWithTheme(<DiffView diff={additionsOnly} />);
209
+
210
+ expect(lastFrame()).toContain("new file");
211
+ expect(lastFrame()).toContain("line 2");
212
+ expect(lastFrame()).toContain("line 3");
213
+ });
214
+
215
+ it("handles diff with only deletions", () => {
216
+ const deletionsOnly = `@@ -1,3 +0,0 @@
217
+ -deleted 1
218
+ -deleted 2
219
+ -deleted 3`;
220
+
221
+ const { lastFrame } = renderWithTheme(<DiffView diff={deletionsOnly} />);
222
+
223
+ expect(lastFrame()).toContain("deleted 1");
224
+ expect(lastFrame()).toContain("deleted 2");
225
+ expect(lastFrame()).toContain("deleted 3");
226
+ });
227
+ });
228
+
229
+ describe("combined props", () => {
230
+ it("works with all props enabled", () => {
231
+ const { lastFrame } = renderWithTheme(
232
+ <DiffView diff={sampleDiff} fileName="src/index.ts" showLineNumbers compact />
233
+ );
234
+
235
+ expect(lastFrame()).toContain("src/index.ts");
236
+ expect(lastFrame()).toContain("foo");
237
+ });
238
+ });
239
+ });
@@ -0,0 +1,303 @@
1
+ /**
2
+ * MarkdownRenderer Component Tests (T020)
3
+ *
4
+ * @module tui/components/Messages/__tests__/MarkdownRenderer.test
5
+ */
6
+
7
+ import { render } from "ink-testing-library";
8
+ import { describe, expect, it } from "vitest";
9
+ import { ThemeProvider } from "../../../theme/index.js";
10
+ import { MarkdownRenderer } from "../MarkdownRenderer.js";
11
+
12
+ /**
13
+ * Wrap component with ThemeProvider for testing.
14
+ */
15
+ function renderWithTheme(ui: React.ReactElement) {
16
+ return render(<ThemeProvider>{ui}</ThemeProvider>);
17
+ }
18
+
19
+ describe("MarkdownRenderer", () => {
20
+ describe("headers", () => {
21
+ it("renders h1 headers with bold styling", () => {
22
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="# Hello World" />);
23
+
24
+ expect(lastFrame()).toContain("Hello World");
25
+ // Header should be present (visual prefix varies)
26
+ expect(lastFrame()).toBeTruthy();
27
+ });
28
+
29
+ it("renders h2 headers", () => {
30
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="## Section Title" />);
31
+
32
+ expect(lastFrame()).toContain("Section Title");
33
+ });
34
+
35
+ it("renders h3 headers", () => {
36
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="### Subsection" />);
37
+
38
+ expect(lastFrame()).toContain("Subsection");
39
+ });
40
+
41
+ it("renders multiple header levels", () => {
42
+ const content = `# Main Title
43
+ ## Section
44
+ ### Subsection`;
45
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
46
+
47
+ expect(lastFrame()).toContain("Main Title");
48
+ expect(lastFrame()).toContain("Section");
49
+ expect(lastFrame()).toContain("Subsection");
50
+ });
51
+ });
52
+
53
+ describe("bold text", () => {
54
+ it("renders **bold** text", () => {
55
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="This is **bold** text" />);
56
+
57
+ expect(lastFrame()).toContain("bold");
58
+ expect(lastFrame()).toContain("This is");
59
+ expect(lastFrame()).toContain("text");
60
+ });
61
+
62
+ it("renders __bold__ text with underscores", () => {
63
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="This is __bold__ text" />);
64
+
65
+ expect(lastFrame()).toContain("bold");
66
+ });
67
+
68
+ it("renders multiple bold sections", () => {
69
+ const { lastFrame } = renderWithTheme(
70
+ <MarkdownRenderer content="**First** and **second** bold" />
71
+ );
72
+
73
+ expect(lastFrame()).toContain("First");
74
+ expect(lastFrame()).toContain("second");
75
+ });
76
+ });
77
+
78
+ describe("italic text", () => {
79
+ it("renders *italic* text with asterisk", () => {
80
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="This is *italic* text" />);
81
+
82
+ expect(lastFrame()).toContain("italic");
83
+ });
84
+
85
+ it("renders _italic_ text with underscore", () => {
86
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="This is _italic_ text" />);
87
+
88
+ expect(lastFrame()).toContain("italic");
89
+ });
90
+ });
91
+
92
+ describe("inline code", () => {
93
+ it("renders `inline code` with backticks", () => {
94
+ const { lastFrame } = renderWithTheme(
95
+ <MarkdownRenderer content="Use `console.log()` for debugging" />
96
+ );
97
+
98
+ expect(lastFrame()).toContain("console.log()");
99
+ expect(lastFrame()).toContain("Use");
100
+ expect(lastFrame()).toContain("debugging");
101
+ });
102
+
103
+ it("renders multiple inline code sections", () => {
104
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="Compare `foo` and `bar`" />);
105
+
106
+ expect(lastFrame()).toContain("foo");
107
+ expect(lastFrame()).toContain("bar");
108
+ });
109
+ });
110
+
111
+ describe("code blocks", () => {
112
+ it("renders code blocks with triple backticks", () => {
113
+ const content = `Here is code:
114
+ \`\`\`
115
+ const x = 1;
116
+ \`\`\``;
117
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
118
+
119
+ expect(lastFrame()).toContain("const x = 1;");
120
+ });
121
+
122
+ it("renders code blocks with language identifier", () => {
123
+ const content = `\`\`\`typescript
124
+ function hello(): void {
125
+ console.log("hi");
126
+ }
127
+ \`\`\``;
128
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
129
+
130
+ expect(lastFrame()).toContain("typescript");
131
+ expect(lastFrame()).toContain("function hello()");
132
+ });
133
+
134
+ it("handles multiline code blocks", () => {
135
+ const content = `\`\`\`
136
+ line 1
137
+ line 2
138
+ line 3
139
+ \`\`\``;
140
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
141
+
142
+ expect(lastFrame()).toContain("line 1");
143
+ expect(lastFrame()).toContain("line 2");
144
+ expect(lastFrame()).toContain("line 3");
145
+ });
146
+ });
147
+
148
+ describe("lists", () => {
149
+ it("renders unordered lists with dash", () => {
150
+ const content = `- Item 1
151
+ - Item 2
152
+ - Item 3`;
153
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
154
+
155
+ expect(lastFrame()).toContain("Item 1");
156
+ expect(lastFrame()).toContain("Item 2");
157
+ expect(lastFrame()).toContain("Item 3");
158
+ expect(lastFrame()).toContain("•");
159
+ });
160
+
161
+ it("renders unordered lists with asterisk", () => {
162
+ const content = `* First
163
+ * Second`;
164
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
165
+
166
+ expect(lastFrame()).toContain("First");
167
+ expect(lastFrame()).toContain("Second");
168
+ });
169
+
170
+ it("renders ordered lists", () => {
171
+ const content = `1. First item
172
+ 2. Second item
173
+ 3. Third item`;
174
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
175
+
176
+ expect(lastFrame()).toContain("1.");
177
+ expect(lastFrame()).toContain("First item");
178
+ expect(lastFrame()).toContain("2.");
179
+ expect(lastFrame()).toContain("Second item");
180
+ });
181
+
182
+ it("renders nested list items", () => {
183
+ const content = `- Parent
184
+ - Child`;
185
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
186
+
187
+ expect(lastFrame()).toContain("Parent");
188
+ expect(lastFrame()).toContain("Child");
189
+ });
190
+ });
191
+
192
+ describe("links", () => {
193
+ it("renders links with text and URL", () => {
194
+ const { lastFrame } = renderWithTheme(
195
+ <MarkdownRenderer content="Visit [GitHub](https://github.com) for code" />
196
+ );
197
+
198
+ expect(lastFrame()).toContain("GitHub");
199
+ expect(lastFrame()).toContain("https://github.com");
200
+ });
201
+
202
+ it("renders multiple links", () => {
203
+ const { lastFrame } = renderWithTheme(
204
+ <MarkdownRenderer content="[Link 1](url1) and [Link 2](url2)" />
205
+ );
206
+
207
+ expect(lastFrame()).toContain("Link 1");
208
+ expect(lastFrame()).toContain("Link 2");
209
+ });
210
+ });
211
+
212
+ describe("mixed content", () => {
213
+ it("renders complex markdown with multiple elements", () => {
214
+ const content = `# Title
215
+
216
+ This is **bold** and *italic* text with \`code\`.
217
+
218
+ ## List Section
219
+
220
+ - Item with **bold**
221
+ - Item with \`code\`
222
+
223
+ \`\`\`javascript
224
+ const hello = "world";
225
+ \`\`\`
226
+
227
+ Check [docs](https://example.com)`;
228
+
229
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
230
+
231
+ expect(lastFrame()).toContain("Title");
232
+ expect(lastFrame()).toContain("bold");
233
+ expect(lastFrame()).toContain("italic");
234
+ expect(lastFrame()).toContain("code");
235
+ expect(lastFrame()).toContain("List Section");
236
+ expect(lastFrame()).toContain("•");
237
+ expect(lastFrame()).toContain("javascript");
238
+ expect(lastFrame()).toContain("hello");
239
+ expect(lastFrame()).toContain("docs");
240
+ });
241
+ });
242
+
243
+ describe("compact mode", () => {
244
+ it("renders with reduced spacing in compact mode", () => {
245
+ const content = `# Header
246
+
247
+ Paragraph 1
248
+
249
+ Paragraph 2`;
250
+
251
+ const { lastFrame: normalFrame } = renderWithTheme(
252
+ <MarkdownRenderer content={content} compact={false} />
253
+ );
254
+
255
+ const { lastFrame: compactFrame } = renderWithTheme(
256
+ <MarkdownRenderer content={content} compact={true} />
257
+ );
258
+
259
+ // Both should contain content
260
+ expect(normalFrame()).toContain("Header");
261
+ expect(compactFrame()).toContain("Header");
262
+ });
263
+ });
264
+
265
+ describe("edge cases", () => {
266
+ it("handles empty content", () => {
267
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content="" />);
268
+
269
+ // Should not crash, may render empty
270
+ expect(lastFrame()).toBeDefined();
271
+ });
272
+
273
+ it("handles content with only whitespace", () => {
274
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content=" \n \n " />);
275
+
276
+ expect(lastFrame()).toBeDefined();
277
+ });
278
+
279
+ it("handles unclosed code blocks gracefully", () => {
280
+ const content = `\`\`\`
281
+ code without closing`;
282
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={content} />);
283
+
284
+ expect(lastFrame()).toContain("code without closing");
285
+ });
286
+
287
+ it("handles special characters in content", () => {
288
+ const specialContent = "Special chars: < > &";
289
+ const { lastFrame } = renderWithTheme(<MarkdownRenderer content={specialContent} />);
290
+
291
+ expect(lastFrame()).toContain("Special chars");
292
+ });
293
+
294
+ it("handles consecutive formatting", () => {
295
+ const { lastFrame } = renderWithTheme(
296
+ <MarkdownRenderer content="***bold italic*** should work" />
297
+ );
298
+
299
+ // Verify component renders without crashing
300
+ expect(lastFrame()).toBeDefined();
301
+ });
302
+ });
303
+ });