@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,243 @@
1
+ /**
2
+ * ToolResultPreview Component
3
+ *
4
+ * Renders tool call results with line truncation and format-aware display.
5
+ * Supports different content types: strings, objects, arrays, directory listings.
6
+ *
7
+ * @module tui/components/Messages/ToolResultPreview
8
+ */
9
+
10
+ import { Box, Text } from "ink";
11
+ import type React from "react";
12
+ import { useMemo } from "react";
13
+ import { useTheme } from "../../theme/index.js";
14
+
15
+ // =============================================================================
16
+ // Constants
17
+ // =============================================================================
18
+
19
+ /** Default maximum lines for tool result preview */
20
+ export const TOOL_RESULT_MAX_LINES = 5;
21
+
22
+ /** Maximum lines for shell/bash command output */
23
+ export const SHELL_TOOL_MAX_LINES = 50;
24
+
25
+ /** Shell-related tool names that get more output lines */
26
+ const SHELL_TOOLS = new Set(["bash", "shell", "run_command", "execute_command", "terminal"]);
27
+
28
+ // =============================================================================
29
+ // Types
30
+ // =============================================================================
31
+
32
+ /**
33
+ * Props for the ToolResultPreview component.
34
+ */
35
+ export interface ToolResultPreviewProps {
36
+ /** The result content to display */
37
+ readonly result: unknown;
38
+ /** The tool name (used to determine line limits) */
39
+ readonly toolName: string;
40
+ /** Override the maximum lines to display */
41
+ readonly maxLines?: number;
42
+ }
43
+
44
+ /**
45
+ * A displayable line with optional styling metadata.
46
+ */
47
+ interface DisplayLine {
48
+ readonly text: string;
49
+ /** If true, render with bold + cyan for directories */
50
+ readonly isDirectory?: boolean;
51
+ }
52
+
53
+ // =============================================================================
54
+ // Helper Functions
55
+ // =============================================================================
56
+
57
+ /**
58
+ * Determines if a tool is a shell/command execution tool.
59
+ */
60
+ function isShellTool(toolName: string): boolean {
61
+ const lowerName = toolName.toLowerCase();
62
+ return SHELL_TOOLS.has(lowerName) || lowerName.includes("bash") || lowerName.includes("shell");
63
+ }
64
+
65
+ /**
66
+ * Checks if an item looks like a directory entry.
67
+ */
68
+ function isDirectoryItem(item: unknown): item is { name: string; type?: string } {
69
+ if (!item || typeof item !== "object") {
70
+ return false;
71
+ }
72
+ const obj = item as Record<string, unknown>;
73
+ return typeof obj.name === "string";
74
+ }
75
+
76
+ /**
77
+ * Formats a directory item with type indicator.
78
+ * Directories: bold cyan with trailing slash
79
+ * Files: indented plain text
80
+ */
81
+ function formatDirectoryItem(item: unknown): DisplayLine {
82
+ if (!isDirectoryItem(item)) {
83
+ return { text: String(item) };
84
+ }
85
+ const { name, type } = item;
86
+ const isDir = type === "directory" || type === "dir" || name.endsWith("/");
87
+ if (isDir) {
88
+ const cleanName = name.replace(/\/$/, "");
89
+ return { text: `${cleanName}/`, isDirectory: true };
90
+ }
91
+ return { text: ` ${name}` };
92
+ }
93
+
94
+ /**
95
+ * Creates a plain display line from text.
96
+ */
97
+ function plainLine(text: string): DisplayLine {
98
+ return { text };
99
+ }
100
+
101
+ /**
102
+ * Converts result to displayable lines.
103
+ */
104
+ function resultToLines(result: unknown): DisplayLine[] {
105
+ if (result === null || result === undefined) {
106
+ return [];
107
+ }
108
+
109
+ if (typeof result === "string") {
110
+ return result.split("\n").map(plainLine);
111
+ }
112
+
113
+ if (Array.isArray(result)) {
114
+ // Check if it's a directory listing (items with name/type)
115
+ if (result.length > 0 && isDirectoryItem(result[0])) {
116
+ return result.map(formatDirectoryItem);
117
+ }
118
+ // Generic array - show as JSON lines
119
+ return result
120
+ .map((item) => (typeof item === "string" ? item : JSON.stringify(item, null, 2)))
121
+ .flatMap((s) => s.split("\n"))
122
+ .map(plainLine);
123
+ }
124
+
125
+ if (typeof result === "object") {
126
+ // Check for content field (common in tool results)
127
+ const obj = result as Record<string, unknown>;
128
+ if (typeof obj.content === "string") {
129
+ return obj.content.split("\n").map(plainLine);
130
+ }
131
+ if (typeof obj.output === "string") {
132
+ return obj.output.split("\n").map(plainLine);
133
+ }
134
+ if (typeof obj.text === "string") {
135
+ return obj.text.split("\n").map(plainLine);
136
+ }
137
+ // Check for entries array (directory listings)
138
+ if (Array.isArray(obj.entries)) {
139
+ return obj.entries.map(formatDirectoryItem);
140
+ }
141
+ // Fallback: JSON stringify
142
+ return JSON.stringify(result, null, 2).split("\n").map(plainLine);
143
+ }
144
+
145
+ return [plainLine(String(result))];
146
+ }
147
+
148
+ /**
149
+ * Truncates lines and returns truncation info.
150
+ */
151
+ function truncateLines(
152
+ lines: DisplayLine[],
153
+ maxLines: number
154
+ ): { visibleLines: DisplayLine[]; hiddenCount: number } {
155
+ if (lines.length <= maxLines) {
156
+ return { visibleLines: lines, hiddenCount: 0 };
157
+ }
158
+ return {
159
+ visibleLines: lines.slice(0, maxLines),
160
+ hiddenCount: lines.length - maxLines,
161
+ };
162
+ }
163
+
164
+ // =============================================================================
165
+ // Main Component
166
+ // =============================================================================
167
+
168
+ /**
169
+ * ToolResultPreview displays tool call results with smart truncation.
170
+ *
171
+ * Features:
172
+ * - Codex-style line truncation (5 lines default, 50 for shell)
173
+ * - Format-aware rendering (directory listings, JSON, plain text)
174
+ * - Shows "+ N lines" indicator when truncated
175
+ * - Directories styled with bold cyan, files with indent
176
+ *
177
+ * @example
178
+ * ```tsx
179
+ * <ToolResultPreview
180
+ * result="line1\nline2\nline3\nline4\nline5\nline6"
181
+ * toolName="read_file"
182
+ * />
183
+ * // Output:
184
+ * // line1
185
+ * // line2
186
+ * // line3
187
+ * // line4
188
+ * // line5
189
+ * // ... +1 lines
190
+ * ```
191
+ */
192
+ export function ToolResultPreview({
193
+ result,
194
+ toolName,
195
+ maxLines: maxLinesOverride,
196
+ }: ToolResultPreviewProps): React.JSX.Element | null {
197
+ const { theme } = useTheme();
198
+
199
+ // Determine max lines based on tool type
200
+ const maxLines = useMemo(() => {
201
+ if (maxLinesOverride !== undefined) {
202
+ return maxLinesOverride;
203
+ }
204
+ return isShellTool(toolName) ? SHELL_TOOL_MAX_LINES : TOOL_RESULT_MAX_LINES;
205
+ }, [toolName, maxLinesOverride]);
206
+
207
+ // Convert result to lines and truncate
208
+ const { visibleLines, hiddenCount } = useMemo(() => {
209
+ const allLines = resultToLines(result);
210
+ // Filter out empty trailing lines
211
+ while (allLines.length > 0 && allLines[allLines.length - 1]?.text.trim() === "") {
212
+ allLines.pop();
213
+ }
214
+ return truncateLines(allLines, maxLines);
215
+ }, [result, maxLines]);
216
+
217
+ // Don't render if no content
218
+ if (visibleLines.length === 0) {
219
+ return null;
220
+ }
221
+
222
+ const mutedColor = theme.semantic.text.muted;
223
+
224
+ return (
225
+ <Box flexDirection="column">
226
+ {visibleLines.map((line, index) => (
227
+ <Text
228
+ key={`${index}-${line.text.slice(0, 20)}`}
229
+ wrap="truncate"
230
+ bold={line.isDirectory}
231
+ color={line.isDirectory ? theme.colors.info : undefined}
232
+ >
233
+ {line.text}
234
+ </Text>
235
+ ))}
236
+ {hiddenCount > 0 && (
237
+ <Text color={mutedColor} dimColor>
238
+ ... +{hiddenCount} {hiddenCount === 1 ? "line" : "lines"}
239
+ </Text>
240
+ )}
241
+ </Box>
242
+ );
243
+ }
@@ -0,0 +1,296 @@
1
+ /**
2
+ * CodeBlock Component Tests (T021)
3
+ *
4
+ * @module tui/components/Messages/__tests__/CodeBlock.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 { CodeBlock } from "../CodeBlock.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("CodeBlock", () => {
20
+ describe("basic rendering", () => {
21
+ it("renders code content", () => {
22
+ const { lastFrame } = renderWithTheme(<CodeBlock code="const x = 1;" />);
23
+
24
+ expect(lastFrame()).toContain("const x = 1;");
25
+ });
26
+
27
+ it("renders multi-line code", () => {
28
+ const code = `function hello() {
29
+ return "world";
30
+ }`;
31
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} />);
32
+
33
+ expect(lastFrame()).toContain("function hello()");
34
+ expect(lastFrame()).toContain("return");
35
+ expect(lastFrame()).toContain("world");
36
+ });
37
+
38
+ it("renders empty code block", () => {
39
+ const { lastFrame } = renderWithTheme(<CodeBlock code="" />);
40
+
41
+ // Should render borders at minimum
42
+ expect(lastFrame()).toBeTruthy();
43
+ });
44
+
45
+ it("renders with borders", () => {
46
+ const { lastFrame } = renderWithTheme(<CodeBlock code="test" />);
47
+
48
+ // Check for box-drawing characters (borders)
49
+ expect(lastFrame()).toMatch(/[─│┌┐└┘┬┴├┤┼╭╮╯╰]/);
50
+ });
51
+ });
52
+
53
+ describe("language header", () => {
54
+ it("displays language label when provided", () => {
55
+ const { lastFrame } = renderWithTheme(
56
+ <CodeBlock code="const x = 1;" language="typescript" />
57
+ );
58
+
59
+ expect(lastFrame()).toContain("typescript");
60
+ });
61
+
62
+ it("does not display language label when not provided", () => {
63
+ const { lastFrame } = renderWithTheme(<CodeBlock code="const x = 1;" />);
64
+
65
+ // Should not have a header row for language
66
+ expect(lastFrame()).not.toContain("javascript");
67
+ expect(lastFrame()).not.toContain("typescript");
68
+ });
69
+ });
70
+
71
+ describe("line numbers", () => {
72
+ it("shows line numbers when enabled", () => {
73
+ const code = `line 1
74
+ line 2
75
+ line 3`;
76
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} showLineNumbers />);
77
+
78
+ expect(lastFrame()).toContain("1");
79
+ expect(lastFrame()).toContain("2");
80
+ expect(lastFrame()).toContain("3");
81
+ });
82
+
83
+ it("hides line numbers when disabled", () => {
84
+ const { lastFrame } = renderWithTheme(
85
+ <CodeBlock code="single line" showLineNumbers={false} />
86
+ );
87
+
88
+ // The line number with separator should not be present
89
+ // (only box border │ should exist, not "1│" line number separator)
90
+ expect(lastFrame()).not.toMatch(/\d│/);
91
+ });
92
+
93
+ it("pads line numbers correctly for many lines", () => {
94
+ const lines = Array.from({ length: 15 }, (_, i) => `line ${i + 1}`).join("\n");
95
+ const { lastFrame } = renderWithTheme(<CodeBlock code={lines} showLineNumbers />);
96
+
97
+ // Should have proper padding for double-digit line numbers
98
+ expect(lastFrame()).toContain("line 1");
99
+ expect(lastFrame()).toContain("15");
100
+ });
101
+ });
102
+
103
+ describe("syntax highlighting", () => {
104
+ it("highlights JavaScript keywords", () => {
105
+ const code = "const value = function() { return true; }";
106
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="javascript" />);
107
+
108
+ expect(lastFrame()).toContain("const");
109
+ expect(lastFrame()).toContain("function");
110
+ expect(lastFrame()).toContain("return");
111
+ expect(lastFrame()).toContain("true");
112
+ });
113
+
114
+ it("highlights TypeScript keywords", () => {
115
+ const code = "interface User { readonly name: string; }";
116
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="typescript" />);
117
+
118
+ expect(lastFrame()).toContain("interface");
119
+ expect(lastFrame()).toContain("readonly");
120
+ expect(lastFrame()).toContain("string");
121
+ });
122
+
123
+ it("highlights Python keywords", () => {
124
+ const code = "def hello():\n return None";
125
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="python" />);
126
+
127
+ expect(lastFrame()).toContain("def");
128
+ expect(lastFrame()).toContain("return");
129
+ expect(lastFrame()).toContain("None");
130
+ });
131
+
132
+ it("highlights bash keywords", () => {
133
+ const code = "if [ -f file ]; then\n echo 'found'\nfi";
134
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="bash" />);
135
+
136
+ expect(lastFrame()).toContain("if");
137
+ expect(lastFrame()).toContain("then");
138
+ expect(lastFrame()).toContain("fi");
139
+ });
140
+
141
+ it("highlights strings in quotes", () => {
142
+ const code = 'const msg = "hello world";';
143
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="javascript" />);
144
+
145
+ expect(lastFrame()).toContain("hello world");
146
+ });
147
+
148
+ it("highlights comments", () => {
149
+ const code = "// This is a comment\nconst x = 1;";
150
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="javascript" />);
151
+
152
+ expect(lastFrame()).toContain("This is a comment");
153
+ });
154
+
155
+ it("handles language aliases (js -> javascript)", () => {
156
+ const code = "const x = 1;";
157
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="js" />);
158
+
159
+ expect(lastFrame()).toContain("const");
160
+ });
161
+
162
+ it("handles language aliases (ts -> typescript)", () => {
163
+ const code = "const x: number = 1;";
164
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="ts" />);
165
+
166
+ expect(lastFrame()).toContain("number");
167
+ });
168
+
169
+ it("falls back to plain text for unknown languages", () => {
170
+ const code = "some code here";
171
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="unknownlang" />);
172
+
173
+ expect(lastFrame()).toContain("some code here");
174
+ });
175
+ });
176
+
177
+ describe("line highlighting", () => {
178
+ it("highlights specified lines", () => {
179
+ const code = `line 1
180
+ line 2
181
+ line 3`;
182
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} highlight={[2]} />);
183
+
184
+ // Should contain highlight indicator
185
+ expect(lastFrame()).toContain("▶");
186
+ });
187
+
188
+ it("highlights multiple lines", () => {
189
+ const code = `line 1
190
+ line 2
191
+ line 3
192
+ line 4`;
193
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} highlight={[1, 3]} />);
194
+
195
+ // Multiple lines should be highlighted
196
+ const output = lastFrame() ?? "";
197
+ const arrowCount = (output.match(/▶/g) ?? []).length;
198
+ expect(arrowCount).toBe(2);
199
+ });
200
+
201
+ it("ignores invalid line numbers", () => {
202
+ const code = "single line";
203
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} highlight={[5, 10]} />);
204
+
205
+ // Should render without error
206
+ expect(lastFrame()).toContain("single line");
207
+ });
208
+ });
209
+
210
+ describe("copy button", () => {
211
+ it("shows copy button when enabled", () => {
212
+ const { lastFrame } = renderWithTheme(<CodeBlock code="test" showCopyButton />);
213
+
214
+ // Translation key is used in tests
215
+ expect(lastFrame()).toContain("code.copy");
216
+ });
217
+
218
+ it("hides copy button when disabled", () => {
219
+ const { lastFrame } = renderWithTheme(<CodeBlock code="test" showCopyButton={false} />);
220
+
221
+ // Translation key should not be present
222
+ expect(lastFrame()).not.toContain("code.copy");
223
+ });
224
+ });
225
+
226
+ describe("maxHeight", () => {
227
+ it("truncates code when exceeding maxHeight", () => {
228
+ const code = Array.from({ length: 20 }, (_, i) => `line ${i + 1}`).join("\n");
229
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} maxHeight={5} />);
230
+
231
+ expect(lastFrame()).toContain("more lines");
232
+ });
233
+
234
+ it("shows all lines when within maxHeight", () => {
235
+ const code = `line 1
236
+ line 2
237
+ line 3`;
238
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} maxHeight={10} />);
239
+
240
+ expect(lastFrame()).not.toContain("more lines");
241
+ });
242
+
243
+ it("displays correct count of hidden lines", () => {
244
+ const code = Array.from({ length: 20 }, (_, i) => `line ${i + 1}`).join("\n");
245
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} maxHeight={5} />);
246
+
247
+ expect(lastFrame()).toContain("15 more lines");
248
+ });
249
+ });
250
+
251
+ describe("JSON highlighting", () => {
252
+ it("highlights JSON values", () => {
253
+ const code = '{"name": "test", "count": 42, "active": true}';
254
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="json" />);
255
+
256
+ expect(lastFrame()).toContain("name");
257
+ expect(lastFrame()).toContain("test");
258
+ expect(lastFrame()).toContain("42");
259
+ expect(lastFrame()).toContain("true");
260
+ });
261
+
262
+ it("highlights null in JSON", () => {
263
+ const code = '{"value": null}';
264
+ const { lastFrame } = renderWithTheme(<CodeBlock code={code} language="json" />);
265
+
266
+ expect(lastFrame()).toContain("null");
267
+ });
268
+ });
269
+
270
+ describe("combined features", () => {
271
+ it("renders with all features enabled", () => {
272
+ const code = `function example() {
273
+ // A comment
274
+ const message = "hello";
275
+ return message;
276
+ }`;
277
+ const { lastFrame } = renderWithTheme(
278
+ <CodeBlock
279
+ code={code}
280
+ language="typescript"
281
+ showLineNumbers
282
+ showCopyButton
283
+ highlight={[3]}
284
+ maxHeight={10}
285
+ />
286
+ );
287
+
288
+ expect(lastFrame()).toContain("typescript");
289
+ // Translation key is used in tests
290
+ expect(lastFrame()).toContain("code.copy");
291
+ expect(lastFrame()).toContain("▶");
292
+ expect(lastFrame()).toContain("1");
293
+ expect(lastFrame()).toContain("function");
294
+ });
295
+ });
296
+ });