@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,284 @@
1
+ /**
2
+ * useFileSuggestions Hook
3
+ *
4
+ * Provides file and folder suggestions for @ mention autocomplete.
5
+ * Scans the file system and returns matching suggestions based on partial path input.
6
+ *
7
+ * @module tui/hooks/useFileSuggestions
8
+ */
9
+
10
+ import * as fs from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ import { useCallback, useEffect, useState } from "react";
13
+ import type { FileSuggestion } from "../components/Input/MentionAutocomplete.js";
14
+
15
+ // =============================================================================
16
+ // Types
17
+ // =============================================================================
18
+
19
+ /**
20
+ * Options for file suggestions.
21
+ */
22
+ export interface UseFileSuggestionsOptions {
23
+ /** Working directory for resolving paths */
24
+ readonly cwd: string;
25
+ /** Whether to include files (default: true) */
26
+ readonly includeFiles?: boolean;
27
+ /** Whether to include directories (default: true) */
28
+ readonly includeDirectories?: boolean;
29
+ /** File extensions to filter (e.g., ['.ts', '.tsx']) */
30
+ readonly extensions?: readonly string[];
31
+ /** Maximum number of suggestions */
32
+ readonly maxSuggestions?: number;
33
+ /** Debounce delay in ms */
34
+ readonly debounceMs?: number;
35
+ }
36
+
37
+ /**
38
+ * Result of the useFileSuggestions hook.
39
+ */
40
+ export interface UseFileSuggestionsResult {
41
+ /** Current file/folder suggestions */
42
+ readonly suggestions: readonly FileSuggestion[];
43
+ /** Whether suggestions are loading */
44
+ readonly loading: boolean;
45
+ /** Error message if scan failed */
46
+ readonly error: string | null;
47
+ /** Refresh suggestions manually */
48
+ readonly refresh: () => void;
49
+ }
50
+
51
+ // =============================================================================
52
+ // Constants
53
+ // =============================================================================
54
+
55
+ /** Directories to skip when scanning */
56
+ const SKIP_DIRECTORIES = new Set([
57
+ "node_modules",
58
+ ".git",
59
+ ".svn",
60
+ ".hg",
61
+ "__pycache__",
62
+ ".cache",
63
+ "dist",
64
+ "build",
65
+ "coverage",
66
+ ".next",
67
+ ".nuxt",
68
+ ".output",
69
+ "target",
70
+ "vendor",
71
+ ]);
72
+
73
+ /** Default options */
74
+ const DEFAULT_OPTIONS: Required<Omit<UseFileSuggestionsOptions, "cwd">> = {
75
+ includeFiles: true,
76
+ includeDirectories: true,
77
+ extensions: [],
78
+ maxSuggestions: 50,
79
+ debounceMs: 100,
80
+ };
81
+
82
+ // =============================================================================
83
+ // Hook Implementation
84
+ // =============================================================================
85
+
86
+ /**
87
+ * Hook to provide file/folder suggestions for a partial path.
88
+ *
89
+ * @param partialPath - The partial path entered by the user
90
+ * @param options - Configuration options
91
+ * @returns File suggestions, loading state, and error
92
+ *
93
+ * @example
94
+ * ```tsx
95
+ * const { suggestions, loading } = useFileSuggestions("./src/", {
96
+ * cwd: "/project",
97
+ * includeFiles: true,
98
+ * includeDirectories: true,
99
+ * });
100
+ * ```
101
+ */
102
+ export function useFileSuggestions(
103
+ partialPath: string,
104
+ options: UseFileSuggestionsOptions
105
+ ): UseFileSuggestionsResult {
106
+ const [suggestions, setSuggestions] = useState<FileSuggestion[]>([]);
107
+ const [loading, setLoading] = useState(false);
108
+ const [error, setError] = useState<string | null>(null);
109
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
110
+
111
+ // Memoize options to avoid recreating object on each render
112
+ const cwd = options.cwd;
113
+ const includeFiles = options.includeFiles ?? DEFAULT_OPTIONS.includeFiles;
114
+ const includeDirectories = options.includeDirectories ?? DEFAULT_OPTIONS.includeDirectories;
115
+ const extensions = options.extensions ?? DEFAULT_OPTIONS.extensions;
116
+ const maxSuggestions = options.maxSuggestions ?? DEFAULT_OPTIONS.maxSuggestions;
117
+ const debounceMs = options.debounceMs ?? DEFAULT_OPTIONS.debounceMs;
118
+
119
+ const refresh = useCallback(() => {
120
+ setRefreshTrigger((prev) => prev + 1);
121
+ }, []);
122
+
123
+ // biome-ignore lint/correctness/useExhaustiveDependencies: refreshTrigger enables manual refresh
124
+ useEffect(() => {
125
+ let cancelled = false;
126
+ const opts: Required<UseFileSuggestionsOptions> = {
127
+ cwd,
128
+ includeFiles,
129
+ includeDirectories,
130
+ extensions,
131
+ maxSuggestions,
132
+ debounceMs,
133
+ };
134
+
135
+ const timeoutId = setTimeout(async () => {
136
+ if (cancelled) return;
137
+
138
+ setLoading(true);
139
+ setError(null);
140
+
141
+ try {
142
+ const result = await scanForSuggestions(partialPath, opts);
143
+ if (!cancelled) {
144
+ setSuggestions(result);
145
+ }
146
+ } catch (err) {
147
+ if (!cancelled) {
148
+ setError((err as Error).message);
149
+ setSuggestions([]);
150
+ }
151
+ } finally {
152
+ if (!cancelled) {
153
+ setLoading(false);
154
+ }
155
+ }
156
+ }, debounceMs);
157
+
158
+ return () => {
159
+ cancelled = true;
160
+ clearTimeout(timeoutId);
161
+ };
162
+ }, [
163
+ partialPath,
164
+ cwd,
165
+ includeFiles,
166
+ includeDirectories,
167
+ extensions,
168
+ maxSuggestions,
169
+ debounceMs,
170
+ refreshTrigger,
171
+ ]);
172
+
173
+ return { suggestions, loading, error, refresh };
174
+ }
175
+
176
+ // =============================================================================
177
+ // Helper Functions
178
+ // =============================================================================
179
+
180
+ /** Parse partial path into scan directory and match prefix */
181
+ function parsePartialPath(partialPath: string): { scanDir: string; matchPrefix: string } {
182
+ const normalizedPartial = partialPath.replace(/\\/g, "/");
183
+
184
+ if (normalizedPartial.endsWith("/") || normalizedPartial === "") {
185
+ return { scanDir: normalizedPartial || ".", matchPrefix: "" };
186
+ }
187
+
188
+ const lastSlash = normalizedPartial.lastIndexOf("/");
189
+ if (lastSlash === -1) {
190
+ return { scanDir: ".", matchPrefix: normalizedPartial };
191
+ }
192
+
193
+ return {
194
+ scanDir: normalizedPartial.slice(0, lastSlash + 1) || ".",
195
+ matchPrefix: normalizedPartial.slice(lastSlash + 1),
196
+ };
197
+ }
198
+
199
+ /** Check if entry should be included based on filters */
200
+ function shouldIncludeEntry(
201
+ entry: { name: string; isDirectory: () => boolean },
202
+ matchPrefix: string,
203
+ includeFiles: boolean,
204
+ includeDirectories: boolean,
205
+ extensions: readonly string[]
206
+ ): boolean {
207
+ const isDir = entry.isDirectory();
208
+ const lowerName = entry.name.toLowerCase();
209
+ const lowerPrefix = matchPrefix.toLowerCase();
210
+
211
+ // Skip hidden files unless searching for them
212
+ if (entry.name.startsWith(".") && !matchPrefix.startsWith(".")) return false;
213
+
214
+ // Skip ignored directories
215
+ if (isDir && SKIP_DIRECTORIES.has(entry.name)) return false;
216
+
217
+ // Check prefix match
218
+ if (lowerPrefix && !lowerName.startsWith(lowerPrefix)) return false;
219
+
220
+ // Filter by type
221
+ if (isDir && !includeDirectories) return false;
222
+ if (!isDir && !includeFiles) return false;
223
+
224
+ // Filter by extension (only for files)
225
+ if (!isDir && extensions.length > 0) {
226
+ const ext = path.extname(entry.name).toLowerCase();
227
+ if (!extensions.includes(ext)) return false;
228
+ }
229
+
230
+ return true;
231
+ }
232
+
233
+ /**
234
+ * Scan for file/folder suggestions based on partial path.
235
+ */
236
+ async function scanForSuggestions(
237
+ partialPath: string,
238
+ options: Required<UseFileSuggestionsOptions>
239
+ ): Promise<FileSuggestion[]> {
240
+ const { cwd, includeFiles, includeDirectories, extensions, maxSuggestions } = options;
241
+
242
+ const { scanDir, matchPrefix } = parsePartialPath(partialPath);
243
+ const fullScanDir = path.resolve(cwd, scanDir);
244
+
245
+ // Check if directory exists
246
+ try {
247
+ const stats = await fs.stat(fullScanDir);
248
+ if (!stats.isDirectory()) return [];
249
+ } catch {
250
+ return [];
251
+ }
252
+
253
+ // Read and filter directory contents
254
+ const entries = await fs.readdir(fullScanDir, { withFileTypes: true });
255
+ const suggestions: FileSuggestion[] = [];
256
+
257
+ for (const entry of entries) {
258
+ if (!shouldIncludeEntry(entry, matchPrefix, includeFiles, includeDirectories, extensions)) {
259
+ continue;
260
+ }
261
+
262
+ const isDirectory = entry.isDirectory();
263
+ const fullPath = scanDir === "." ? entry.name : `${scanDir}${entry.name}`;
264
+
265
+ suggestions.push({
266
+ name: entry.name,
267
+ path: fullPath,
268
+ isDirectory,
269
+ extension: isDirectory ? undefined : path.extname(entry.name).slice(1),
270
+ });
271
+
272
+ if (suggestions.length >= maxSuggestions) break;
273
+ }
274
+
275
+ // Sort: directories first, then alphabetically
276
+ suggestions.sort((a, b) => {
277
+ if (a.isDirectory !== b.isDirectory) {
278
+ return a.isDirectory ? -1 : 1;
279
+ }
280
+ return a.name.localeCompare(b.name);
281
+ });
282
+
283
+ return suggestions;
284
+ }
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Flicker Detector Hook
3
+ *
4
+ * Detects when content exceeds container bounds, which can cause
5
+ * visual flickering or rendering artifacts in terminal UIs.
6
+ *
7
+ * This hook is essential for preventing TUI rendering issues by
8
+ * detecting overflow conditions before they cause visual problems.
9
+ *
10
+ * Ported from Gemini CLI for Vellum TUI.
11
+ *
12
+ * @module tui/hooks/useFlickerDetector
13
+ */
14
+
15
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
16
+
17
+ // =============================================================================
18
+ // Types
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Options for the flicker detector hook
23
+ */
24
+ export interface UseFlickerDetectorOptions {
25
+ /** Height of the content in rows/lines */
26
+ readonly contentHeight: number;
27
+ /** Height of the container in rows/lines */
28
+ readonly containerHeight: number;
29
+ /**
30
+ * Threshold in rows before considering content as overflowing.
31
+ * A small threshold helps prevent edge-case flickering.
32
+ * @default 0
33
+ */
34
+ readonly threshold?: number;
35
+ /**
36
+ * Enable debouncing to prevent rapid state changes.
37
+ * Useful when content height changes frequently.
38
+ * @default true
39
+ */
40
+ readonly debounce?: boolean;
41
+ /**
42
+ * Debounce delay in milliseconds.
43
+ * @default 50
44
+ */
45
+ readonly debounceDelay?: number;
46
+ }
47
+
48
+ /**
49
+ * Result from the flicker detector hook
50
+ */
51
+ export interface FlickerDetectorResult {
52
+ /** Whether the content is overflowing the container */
53
+ readonly isOverflowing: boolean;
54
+ /** Amount of overflow in rows (negative if content fits) */
55
+ readonly overflow: number;
56
+ /** Percentage of container filled (can exceed 100%) */
57
+ readonly fillPercentage: number;
58
+ /** Whether overflow state recently changed (indicates potential flicker) */
59
+ readonly isTransitioning: boolean;
60
+ }
61
+
62
+ // =============================================================================
63
+ // Constants
64
+ // =============================================================================
65
+
66
+ /** Default debounce delay in milliseconds */
67
+ const DEFAULT_DEBOUNCE_DELAY = 50;
68
+
69
+ /** Transition window for detecting rapid state changes (ms) */
70
+ const TRANSITION_WINDOW = 100;
71
+
72
+ // =============================================================================
73
+ // Hook Implementation
74
+ // =============================================================================
75
+
76
+ /**
77
+ * Hook to detect content overflow that may cause visual flickering.
78
+ *
79
+ * Terminal UIs can flicker when content rapidly alternates between
80
+ * fitting and overflowing the visible area. This hook provides:
81
+ *
82
+ * 1. **Overflow detection**: Know when content exceeds bounds
83
+ * 2. **Transition tracking**: Detect rapid state changes
84
+ * 3. **Debouncing**: Prevent jittery state updates
85
+ *
86
+ * @param options - Configuration for overflow detection
87
+ * @returns Overflow state and metrics
88
+ *
89
+ * @example
90
+ * ```tsx
91
+ * function MessageList({ messages, containerHeight }) {
92
+ * const contentHeight = messages.length * 3; // Estimate 3 rows per message
93
+ *
94
+ * const { isOverflowing, overflow, isTransitioning } = useFlickerDetector({
95
+ * contentHeight,
96
+ * containerHeight,
97
+ * threshold: 2, // Allow 2-row buffer
98
+ * });
99
+ *
100
+ * if (isOverflowing) {
101
+ * // Enable virtualization or truncation
102
+ * return <VirtualizedList data={messages} />;
103
+ * }
104
+ *
105
+ * // Render all messages directly
106
+ * return messages.map(msg => <Message key={msg.id} {...msg} />);
107
+ * }
108
+ * ```
109
+ */
110
+ export function useFlickerDetector(options: UseFlickerDetectorOptions): FlickerDetectorResult {
111
+ const {
112
+ contentHeight,
113
+ containerHeight,
114
+ threshold = 0,
115
+ debounce = true,
116
+ debounceDelay = DEFAULT_DEBOUNCE_DELAY,
117
+ } = options;
118
+
119
+ // Track the raw overflow state
120
+ const [debouncedOverflow, setDebouncedOverflow] = useState(() => {
121
+ return contentHeight - containerHeight > threshold;
122
+ });
123
+
124
+ // Track rapid transitions
125
+ const [isTransitioning, setIsTransitioning] = useState(false);
126
+ const lastChangeTime = useRef<number>(0);
127
+ const transitionTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
128
+
129
+ /**
130
+ * Calculate raw overflow state (not debounced)
131
+ */
132
+ const rawOverflow = useMemo(() => {
133
+ return contentHeight - containerHeight;
134
+ }, [contentHeight, containerHeight]);
135
+
136
+ const rawIsOverflowing = rawOverflow > threshold;
137
+
138
+ /**
139
+ * Calculate fill percentage
140
+ */
141
+ const fillPercentage = useMemo(() => {
142
+ if (containerHeight <= 0) return 0;
143
+ return (contentHeight / containerHeight) * 100;
144
+ }, [contentHeight, containerHeight]);
145
+
146
+ /**
147
+ * Handle state transitions with optional debouncing
148
+ */
149
+ const updateOverflowState = useCallback((newState: boolean) => {
150
+ const now = Date.now();
151
+ const timeSinceLastChange = now - lastChangeTime.current;
152
+
153
+ // Detect rapid transitions (potential flicker source)
154
+ if (timeSinceLastChange < TRANSITION_WINDOW) {
155
+ setIsTransitioning(true);
156
+
157
+ // Clear existing timeout
158
+ if (transitionTimeout.current) {
159
+ clearTimeout(transitionTimeout.current);
160
+ }
161
+
162
+ // Clear transition flag after window
163
+ transitionTimeout.current = setTimeout(() => {
164
+ setIsTransitioning(false);
165
+ }, TRANSITION_WINDOW);
166
+ }
167
+
168
+ lastChangeTime.current = now;
169
+ setDebouncedOverflow(newState);
170
+ }, []);
171
+
172
+ /**
173
+ * Effect to update overflow state with optional debouncing
174
+ */
175
+ useEffect(() => {
176
+ if (!debounce) {
177
+ // No debouncing - update immediately
178
+ if (rawIsOverflowing !== debouncedOverflow) {
179
+ updateOverflowState(rawIsOverflowing);
180
+ }
181
+ return;
182
+ }
183
+
184
+ // Debounced update
185
+ const timeoutId = setTimeout(() => {
186
+ if (rawIsOverflowing !== debouncedOverflow) {
187
+ updateOverflowState(rawIsOverflowing);
188
+ }
189
+ }, debounceDelay);
190
+
191
+ return () => {
192
+ clearTimeout(timeoutId);
193
+ };
194
+ }, [rawIsOverflowing, debouncedOverflow, debounce, debounceDelay, updateOverflowState]);
195
+
196
+ /**
197
+ * Cleanup transition timeout on unmount
198
+ */
199
+ useEffect(() => {
200
+ return () => {
201
+ if (transitionTimeout.current) {
202
+ clearTimeout(transitionTimeout.current);
203
+ }
204
+ };
205
+ }, []);
206
+
207
+ /**
208
+ * Return memoized result
209
+ */
210
+ return useMemo<FlickerDetectorResult>(
211
+ () => ({
212
+ isOverflowing: debounce ? debouncedOverflow : rawIsOverflowing,
213
+ overflow: rawOverflow,
214
+ fillPercentage,
215
+ isTransitioning,
216
+ }),
217
+ [debounce, debouncedOverflow, rawIsOverflowing, rawOverflow, fillPercentage, isTransitioning]
218
+ );
219
+ }
220
+
221
+ // =============================================================================
222
+ // Utility Functions
223
+ // =============================================================================
224
+
225
+ /**
226
+ * Simple overflow check without the full hook (for one-off calculations).
227
+ *
228
+ * @param contentHeight - Height of the content
229
+ * @param containerHeight - Height of the container
230
+ * @param threshold - Buffer threshold (default: 0)
231
+ * @returns Whether content is overflowing
232
+ */
233
+ export function isContentOverflowing(
234
+ contentHeight: number,
235
+ containerHeight: number,
236
+ threshold = 0
237
+ ): boolean {
238
+ return contentHeight - containerHeight > threshold;
239
+ }
240
+
241
+ /**
242
+ * Calculate recommended container height to fit content with buffer.
243
+ *
244
+ * @param contentHeight - Height of the content
245
+ * @param bufferRows - Additional buffer rows (default: 2)
246
+ * @returns Recommended container height
247
+ */
248
+ export function calculateSafeContainerHeight(contentHeight: number, bufferRows = 2): number {
249
+ return contentHeight + bufferRows;
250
+ }