@alumbwe/anvil 1.0.18 → 1.0.20

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 (494) hide show
  1. package/README.md +28 -30
  2. package/bin/anvil +2 -0
  3. package/package.json +71 -75
  4. package/src/agents/bundled-agents.generated.d.ts +14 -0
  5. package/src/agents/bundled-agents.generated.ts +3760 -0
  6. package/src/app.tsx +399 -0
  7. package/src/chat.tsx +1698 -0
  8. package/src/cli-args.ts +134 -0
  9. package/src/commands/__tests__/anvil-command-aliases.test.ts +56 -0
  10. package/src/commands/__tests__/bash-command.test.ts +445 -0
  11. package/src/commands/__tests__/command-args.test.ts +349 -0
  12. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
  13. package/src/commands/__tests__/copy-conversation.test.ts +226 -0
  14. package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
  15. package/src/commands/__tests__/image.test.ts +99 -0
  16. package/src/commands/__tests__/init.test.ts +456 -0
  17. package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
  18. package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
  19. package/src/commands/__tests__/prompt-builders.test.ts +28 -0
  20. package/src/commands/__tests__/router-input.test.ts +298 -0
  21. package/src/commands/ads.ts +44 -0
  22. package/src/commands/command-registry.ts +726 -0
  23. package/src/commands/copy-conversation.ts +392 -0
  24. package/src/commands/help.ts +13 -0
  25. package/src/commands/image.ts +20 -0
  26. package/src/commands/init.ts +121 -0
  27. package/src/commands/process-diagnostics.ts +120 -0
  28. package/src/commands/prompt-builders.ts +100 -0
  29. package/src/commands/publish.ts +203 -0
  30. package/src/commands/router-utils.ts +80 -0
  31. package/src/commands/router.ts +487 -0
  32. package/src/commands/usage.ts +26 -0
  33. package/src/components/__tests__/ad-banner.test.tsx +83 -0
  34. package/src/components/__tests__/anvil-landing-heading-row.test.tsx +91 -0
  35. package/src/components/__tests__/anvil-model-selector.test.tsx +519 -0
  36. package/src/components/__tests__/anvil-offer-invariants.test.ts +54 -0
  37. package/src/components/__tests__/anvil-takeover-prompt.test.tsx +212 -0
  38. package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
  39. package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
  40. package/src/components/__tests__/grid-layout.test.tsx +1051 -0
  41. package/src/components/__tests__/message-block.completion.test.tsx +104 -0
  42. package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
  43. package/src/components/__tests__/message-with-agents.test.tsx +579 -0
  44. package/src/components/__tests__/multiline-input.test.tsx +1132 -0
  45. package/src/components/__tests__/queue-panel.test.tsx +398 -0
  46. package/src/components/__tests__/selectable-list.test.ts +232 -0
  47. package/src/components/__tests__/status-bar.test.tsx +44 -0
  48. package/src/components/__tests__/status-indicator.test.tsx +204 -0
  49. package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
  50. package/src/components/__tests__/user-error-banner.test.tsx +102 -0
  51. package/src/components/ad-banner.tsx +323 -0
  52. package/src/components/agent-checklist.tsx +350 -0
  53. package/src/components/agent-mode-toggle.tsx +241 -0
  54. package/src/components/anvil-active-session-summary.tsx +63 -0
  55. package/src/components/anvil-landing-screen.tsx +865 -0
  56. package/src/components/anvil-mark.tsx +52 -0
  57. package/src/components/anvil-model-selector.tsx +1087 -0
  58. package/src/components/anvil-referral-banner.tsx +604 -0
  59. package/src/components/anvil-superseded-screen.tsx +62 -0
  60. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
  61. package/src/components/ask-user/__tests__/validation.test.ts +213 -0
  62. package/src/components/ask-user/components/accordion-question.tsx +153 -0
  63. package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
  64. package/src/components/ask-user/components/options-list.tsx +133 -0
  65. package/src/components/ask-user/components/question-header.tsx +80 -0
  66. package/src/components/ask-user/components/question-option.tsx +89 -0
  67. package/src/components/ask-user/constants.ts +38 -0
  68. package/src/components/ask-user/index.tsx +631 -0
  69. package/src/components/ask-user/utils/validation.ts +85 -0
  70. package/src/components/attachment-card.tsx +65 -0
  71. package/src/components/blocks/agent-block-grid.tsx +58 -0
  72. package/src/components/blocks/agent-branch-item.tsx +311 -0
  73. package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
  74. package/src/components/blocks/agent-list-branch.tsx +77 -0
  75. package/src/components/blocks/ask-user-branch.tsx +81 -0
  76. package/src/components/blocks/block-helpers.ts +11 -0
  77. package/src/components/blocks/blocks-renderer.tsx +289 -0
  78. package/src/components/blocks/content-with-markdown.tsx +33 -0
  79. package/src/components/blocks/image-block.tsx +128 -0
  80. package/src/components/blocks/implementor-row.tsx +429 -0
  81. package/src/components/blocks/single-block.tsx +201 -0
  82. package/src/components/blocks/thinking-block.tsx +67 -0
  83. package/src/components/blocks/tool-block-group.tsx +57 -0
  84. package/src/components/blocks/tool-branch.tsx +206 -0
  85. package/src/components/blocks/user-content-copy.tsx +157 -0
  86. package/src/components/bottom-banner.tsx +136 -0
  87. package/src/components/build-mode-buttons.tsx +110 -0
  88. package/src/components/button.tsx +66 -0
  89. package/src/components/chat-header.tsx +77 -0
  90. package/src/components/chat-history-screen.tsx +413 -0
  91. package/src/components/chat-input-bar.tsx +523 -0
  92. package/src/components/clickable-title-box.tsx +66 -0
  93. package/src/components/clickable.tsx +119 -0
  94. package/src/components/collapse-button.tsx +34 -0
  95. package/src/components/copy-button.tsx +203 -0
  96. package/src/components/elapsed-timer.tsx +55 -0
  97. package/src/components/error-boundary.tsx +55 -0
  98. package/src/components/feedback-container.tsx +181 -0
  99. package/src/components/feedback-icon-button.tsx +89 -0
  100. package/src/components/feedback-input-mode.tsx +344 -0
  101. package/src/components/file-attachment-card.tsx +98 -0
  102. package/src/components/grid-layout.tsx +80 -0
  103. package/src/components/help-banner.tsx +127 -0
  104. package/src/components/highlighted-text.tsx +52 -0
  105. package/src/components/image-card.tsx +167 -0
  106. package/src/components/image-thumbnail.tsx +114 -0
  107. package/src/components/input-cursor.tsx +77 -0
  108. package/src/components/input-mode-banner.tsx +57 -0
  109. package/src/components/load-previous-button.tsx +48 -0
  110. package/src/components/login-modal.tsx +475 -0
  111. package/src/components/message-block.tsx +346 -0
  112. package/src/components/message-footer.tsx +258 -0
  113. package/src/components/message-with-agents.tsx +506 -0
  114. package/src/components/mode-divider.tsx +43 -0
  115. package/src/components/multiline-input.tsx +1241 -0
  116. package/src/components/out-of-credits-banner.tsx +160 -0
  117. package/src/components/pending-attachments-banner.tsx +116 -0
  118. package/src/components/pending-bash-message.tsx +59 -0
  119. package/src/components/progress-bar.tsx +81 -0
  120. package/src/components/project-picker-screen.tsx +485 -0
  121. package/src/components/publish-confirmation.tsx +493 -0
  122. package/src/components/publish-container.tsx +687 -0
  123. package/src/components/queue-panel.tsx +310 -0
  124. package/src/components/raised-pill.tsx +95 -0
  125. package/src/components/renderers/plan-box.tsx +59 -0
  126. package/src/components/review-screen.tsx +112 -0
  127. package/src/components/scroll-to-bottom-button.tsx +34 -0
  128. package/src/components/segmented-control.tsx +187 -0
  129. package/src/components/selectable-list.tsx +248 -0
  130. package/src/components/selected-chips.tsx +80 -0
  131. package/src/components/separator.tsx +40 -0
  132. package/src/components/session-ended-banner.tsx +226 -0
  133. package/src/components/shimmer-text.tsx +248 -0
  134. package/src/components/status-bar.tsx +313 -0
  135. package/src/components/subscription-limit-banner.tsx +206 -0
  136. package/src/components/suggested-prompts.tsx +184 -0
  137. package/src/components/suggestion-menu.tsx +215 -0
  138. package/src/components/terminal-command-display.tsx +151 -0
  139. package/src/components/terminal-link.tsx +97 -0
  140. package/src/components/terminal-thinking-orb.tsx +95 -0
  141. package/src/components/text-attachment-card.tsx +77 -0
  142. package/src/components/thinking.tsx +109 -0
  143. package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
  144. package/src/components/tools/__tests__/code-search.test.tsx +45 -0
  145. package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
  146. package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
  147. package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
  148. package/src/components/tools/apply-patch.tsx +95 -0
  149. package/src/components/tools/code-search.tsx +45 -0
  150. package/src/components/tools/composio.tsx +152 -0
  151. package/src/components/tools/diff-viewer.tsx +76 -0
  152. package/src/components/tools/glob.tsx +61 -0
  153. package/src/components/tools/gravity-index.tsx +77 -0
  154. package/src/components/tools/list-directory.tsx +61 -0
  155. package/src/components/tools/read-docs.tsx +37 -0
  156. package/src/components/tools/read-files.tsx +91 -0
  157. package/src/components/tools/read-subtree.tsx +44 -0
  158. package/src/components/tools/read-url.tsx +33 -0
  159. package/src/components/tools/registry.ts +126 -0
  160. package/src/components/tools/render-ui.tsx +122 -0
  161. package/src/components/tools/run-terminal-command.tsx +79 -0
  162. package/src/components/tools/skill.tsx +29 -0
  163. package/src/components/tools/str-replace.tsx +77 -0
  164. package/src/components/tools/suggest-followups.tsx +369 -0
  165. package/src/components/tools/task-completed.tsx +16 -0
  166. package/src/components/tools/tool-call-item.tsx +273 -0
  167. package/src/components/tools/types.ts +55 -0
  168. package/src/components/tools/web-search.tsx +33 -0
  169. package/src/components/tools/write-file.tsx +16 -0
  170. package/src/components/tools/write-todos.tsx +95 -0
  171. package/src/components/top-banner.tsx +178 -0
  172. package/src/components/usage-banner.tsx +244 -0
  173. package/src/components/user-error-banner.tsx +56 -0
  174. package/src/components/validation-error-popover.tsx +192 -0
  175. package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
  176. package/src/contexts/chat-runtime-context.tsx +240 -0
  177. package/src/data/slash-commands.test.ts +24 -0
  178. package/src/data/slash-commands.ts +249 -0
  179. package/src/entry.ts +12 -0
  180. package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +49 -0
  181. package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
  182. package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
  183. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
  184. package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
  185. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
  186. package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
  187. package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
  188. package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
  189. package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
  190. package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
  191. package/src/hooks/__tests__/use-input-history.test.ts +699 -0
  192. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
  193. package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
  194. package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
  195. package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
  196. package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
  197. package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
  198. package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
  199. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
  200. package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
  201. package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
  202. package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
  203. package/src/hooks/__tests__/use-timeout.test.ts +330 -0
  204. package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
  205. package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
  206. package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
  207. package/src/hooks/helpers/send-message.ts +654 -0
  208. package/src/hooks/stream-state.ts +83 -0
  209. package/src/hooks/use-activity-query.ts +668 -0
  210. package/src/hooks/use-agent-validation.ts +74 -0
  211. package/src/hooks/use-anvil-ctrl-c-exit.ts +23 -0
  212. package/src/hooks/use-anvil-session-progress.ts +34 -0
  213. package/src/hooks/use-anvil-session.ts +806 -0
  214. package/src/hooks/use-anvil-streak-query.ts +61 -0
  215. package/src/hooks/use-ask-user-bridge.ts +92 -0
  216. package/src/hooks/use-auth-query.ts +262 -0
  217. package/src/hooks/use-auth-state.ts +152 -0
  218. package/src/hooks/use-chat-input.ts +105 -0
  219. package/src/hooks/use-chat-keyboard.ts +321 -0
  220. package/src/hooks/use-chat-messages.ts +254 -0
  221. package/src/hooks/use-chat-state.ts +165 -0
  222. package/src/hooks/use-chat-streaming.ts +180 -0
  223. package/src/hooks/use-chat-ui.ts +131 -0
  224. package/src/hooks/use-clipboard.ts +151 -0
  225. package/src/hooks/use-connection-status.ts +142 -0
  226. package/src/hooks/use-directory-browser.ts +88 -0
  227. package/src/hooks/use-elapsed-time.ts +116 -0
  228. package/src/hooks/use-event.ts +34 -0
  229. package/src/hooks/use-exit-handler.ts +66 -0
  230. package/src/hooks/use-fetch-login-url.ts +65 -0
  231. package/src/hooks/use-fingerprint.ts +61 -0
  232. package/src/hooks/use-gravity-ad.ts +656 -0
  233. package/src/hooks/use-grid-layout.ts +73 -0
  234. package/src/hooks/use-input-history.ts +173 -0
  235. package/src/hooks/use-login-keyboard-handlers.ts +75 -0
  236. package/src/hooks/use-login-polling.ts +114 -0
  237. package/src/hooks/use-logo.tsx +161 -0
  238. package/src/hooks/use-message-queue.ts +387 -0
  239. package/src/hooks/use-now.ts +20 -0
  240. package/src/hooks/use-path-tab-completion.ts +88 -0
  241. package/src/hooks/use-publish-mutation.ts +60 -0
  242. package/src/hooks/use-queue-controls.ts +68 -0
  243. package/src/hooks/use-queue-ui.ts +74 -0
  244. package/src/hooks/use-scroll-management.ts +184 -0
  245. package/src/hooks/use-searchable-list.ts +98 -0
  246. package/src/hooks/use-send-message.ts +773 -0
  247. package/src/hooks/use-sheen-animation.tsx +88 -0
  248. package/src/hooks/use-subscription-query.ts +71 -0
  249. package/src/hooks/use-suggestion-engine.ts +783 -0
  250. package/src/hooks/use-terminal-breakpoints.ts +77 -0
  251. package/src/hooks/use-terminal-dimensions.ts +46 -0
  252. package/src/hooks/use-terminal-focus.ts +29 -0
  253. package/src/hooks/use-terminal-layout.ts +189 -0
  254. package/src/hooks/use-theme.tsx +140 -0
  255. package/src/hooks/use-timeout.ts +85 -0
  256. package/src/hooks/use-update-preference.ts +66 -0
  257. package/src/hooks/use-usage-monitor.ts +55 -0
  258. package/src/hooks/use-usage-query.ts +124 -0
  259. package/src/hooks/use-user-details-query.ts +93 -0
  260. package/src/hooks/use-why-did-you-update.ts +177 -0
  261. package/src/index.tsx +473 -0
  262. package/src/init/__tests__/init-direnv.test.ts +526 -0
  263. package/src/init/init-app.ts +34 -0
  264. package/src/init/init-direnv.ts +133 -0
  265. package/src/login/constants.ts +60 -0
  266. package/src/login/login-flow.ts +204 -0
  267. package/src/login/plain-login.ts +108 -0
  268. package/src/login/utils.ts +183 -0
  269. package/src/native/ripgrep.ts +80 -0
  270. package/src/polyfills/bun-strip-ansi.ts +12 -0
  271. package/src/pre-init/tree-sitter-wasm.ts +96 -0
  272. package/src/project-files.ts +102 -0
  273. package/src/setup/key-setup.ts +295 -0
  274. package/src/setup/load-env.ts +106 -0
  275. package/src/state/__tests__/chat-store-focus.test.ts +22 -0
  276. package/src/state/__tests__/feedback-store.test.ts +257 -0
  277. package/src/state/anvil-model-store.ts +42 -0
  278. package/src/state/anvil-session-store.ts +53 -0
  279. package/src/state/chat-history-store.ts +30 -0
  280. package/src/state/chat-store.ts +544 -0
  281. package/src/state/feedback-store.ts +160 -0
  282. package/src/state/login-store.ts +150 -0
  283. package/src/state/message-block-store.ts +149 -0
  284. package/src/state/publish-store.ts +146 -0
  285. package/src/state/queue-panel-store.ts +26 -0
  286. package/src/state/review-store.ts +24 -0
  287. package/src/types/anvil-session.ts +17 -0
  288. package/src/types/chat-state.ts +15 -0
  289. package/src/types/chat.ts +241 -0
  290. package/src/types/contracts/send-message.ts +12 -0
  291. package/src/types/env.ts +98 -0
  292. package/src/types/function-params.ts +28 -0
  293. package/src/types/react19-compat.d.ts +19 -0
  294. package/src/types/store.ts +111 -0
  295. package/src/types/theme-system.ts +144 -0
  296. package/src/types/utils.ts +3 -0
  297. package/src/utils/__tests__/active-run.test.ts +140 -0
  298. package/src/utils/__tests__/activity-tracker.test.ts +440 -0
  299. package/src/utils/__tests__/agent-display.test.ts +139 -0
  300. package/src/utils/__tests__/analytics-client.test.ts +262 -0
  301. package/src/utils/__tests__/anonymous-id.test.ts +74 -0
  302. package/src/utils/__tests__/anvil-api.test.ts +578 -0
  303. package/src/utils/__tests__/anvil-instance-owner.test.ts +69 -0
  304. package/src/utils/__tests__/anvil-model-navigation.test.ts +102 -0
  305. package/src/utils/__tests__/anvil-premium-reset.test.ts +79 -0
  306. package/src/utils/__tests__/anvil-referral-cache.test.ts +116 -0
  307. package/src/utils/__tests__/anvil-session-api.test.ts +215 -0
  308. package/src/utils/__tests__/anvil-session-display.test.ts +23 -0
  309. package/src/utils/__tests__/anvil-streak-line.test.ts +217 -0
  310. package/src/utils/__tests__/arrays.test.ts +264 -0
  311. package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
  312. package/src/utils/__tests__/block-processor.test.ts +810 -0
  313. package/src/utils/__tests__/chat-history.test.ts +203 -0
  314. package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
  315. package/src/utils/__tests__/chat-meta.test.ts +130 -0
  316. package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
  317. package/src/utils/__tests__/clipboard.test.ts +910 -0
  318. package/src/utils/__tests__/code-search-summary.test.ts +84 -0
  319. package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
  320. package/src/utils/__tests__/error-handling.test.ts +658 -0
  321. package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
  322. package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
  323. package/src/utils/__tests__/feedback-submission.test.ts +26 -0
  324. package/src/utils/__tests__/fetch-usage.test.ts +355 -0
  325. package/src/utils/__tests__/fingerprint.test.ts +144 -0
  326. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
  327. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
  328. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
  329. package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
  330. package/src/utils/__tests__/format-timeout.test.ts +87 -0
  331. package/src/utils/__tests__/image-dimensions.test.ts +249 -0
  332. package/src/utils/__tests__/image-processor.test.ts +209 -0
  333. package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
  334. package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
  335. package/src/utils/__tests__/layout-helpers.test.ts +36 -0
  336. package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
  337. package/src/utils/__tests__/log-shipper.test.ts +53 -0
  338. package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
  339. package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
  340. package/src/utils/__tests__/message-updater.test.ts +645 -0
  341. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
  342. package/src/utils/__tests__/pending-attachments.test.ts +277 -0
  343. package/src/utils/__tests__/polling-backoff.test.ts +51 -0
  344. package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
  345. package/src/utils/__tests__/run-state-storage.test.ts +919 -0
  346. package/src/utils/__tests__/safe-json.test.ts +77 -0
  347. package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
  348. package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
  349. package/src/utils/__tests__/send-message-timer.test.ts +64 -0
  350. package/src/utils/__tests__/settings.test.ts +99 -0
  351. package/src/utils/__tests__/strings.test.ts +192 -0
  352. package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
  353. package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
  354. package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
  355. package/src/utils/__tests__/terminal-io.test.ts +137 -0
  356. package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
  357. package/src/utils/__tests__/text-layout.test.ts +82 -0
  358. package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
  359. package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
  360. package/src/utils/__tests__/trace-writer.test.ts +223 -0
  361. package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
  362. package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
  363. package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
  364. package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
  365. package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
  366. package/src/utils/active-run.ts +131 -0
  367. package/src/utils/activity-tracker.ts +66 -0
  368. package/src/utils/agent-display.ts +87 -0
  369. package/src/utils/agent-helpers.ts +27 -0
  370. package/src/utils/agent-id-utils.ts +14 -0
  371. package/src/utils/ai-message-id.ts +12 -0
  372. package/src/utils/analytics.ts +361 -0
  373. package/src/utils/anonymous-id.ts +87 -0
  374. package/src/utils/anvil-agent-selection.ts +33 -0
  375. package/src/utils/anvil-api.ts +612 -0
  376. package/src/utils/anvil-client.ts +217 -0
  377. package/src/utils/anvil-instance-owner.ts +66 -0
  378. package/src/utils/anvil-model-navigation.ts +50 -0
  379. package/src/utils/anvil-premium-reset.ts +55 -0
  380. package/src/utils/anvil-referral-cache.ts +51 -0
  381. package/src/utils/anvil-session-api.ts +243 -0
  382. package/src/utils/anvil-session-display.ts +21 -0
  383. package/src/utils/anvil-streak-line.ts +93 -0
  384. package/src/utils/arrays.ts +117 -0
  385. package/src/utils/auth.ts +258 -0
  386. package/src/utils/bash-context-processor.ts +47 -0
  387. package/src/utils/bash-messages.ts +108 -0
  388. package/src/utils/block-margins.ts +35 -0
  389. package/src/utils/block-operations.ts +523 -0
  390. package/src/utils/block-processor.ts +213 -0
  391. package/src/utils/chat-history.ts +253 -0
  392. package/src/utils/chat-input-key-intercept.ts +52 -0
  393. package/src/utils/chat-meta.ts +90 -0
  394. package/src/utils/chat-scroll-accel.ts +151 -0
  395. package/src/utils/clipboard-image.ts +588 -0
  396. package/src/utils/clipboard.ts +397 -0
  397. package/src/utils/code-search-summary.ts +70 -0
  398. package/src/utils/collapse-helpers.ts +256 -0
  399. package/src/utils/config-dir.ts +24 -0
  400. package/src/utils/constants.ts +190 -0
  401. package/src/utils/create-event-handler-state.ts +73 -0
  402. package/src/utils/create-run-config.ts +121 -0
  403. package/src/utils/detect-shell.ts +116 -0
  404. package/src/utils/directory-browser.ts +72 -0
  405. package/src/utils/engagement.ts +55 -0
  406. package/src/utils/env.ts +92 -0
  407. package/src/utils/error-handling.ts +255 -0
  408. package/src/utils/error-messages.ts +62 -0
  409. package/src/utils/exit-cleanly.ts +86 -0
  410. package/src/utils/feedback-helpers.ts +103 -0
  411. package/src/utils/feedback-submission.ts +22 -0
  412. package/src/utils/fetch-usage.ts +76 -0
  413. package/src/utils/fingerprint.ts +245 -0
  414. package/src/utils/format-elapsed-time.ts +33 -0
  415. package/src/utils/format-session-units.ts +6 -0
  416. package/src/utils/format-timeout.ts +28 -0
  417. package/src/utils/format-validation-errors-for-message.ts +84 -0
  418. package/src/utils/git.ts +17 -0
  419. package/src/utils/helpers.ts +47 -0
  420. package/src/utils/image-display.ts +67 -0
  421. package/src/utils/image-handler.ts +325 -0
  422. package/src/utils/image-processor.ts +133 -0
  423. package/src/utils/image-thumbnail.ts +78 -0
  424. package/src/utils/implementor-helpers.ts +787 -0
  425. package/src/utils/input-modes.ts +174 -0
  426. package/src/utils/keyboard-actions.ts +392 -0
  427. package/src/utils/keypad-keys.ts +47 -0
  428. package/src/utils/layout-helpers.ts +33 -0
  429. package/src/utils/lazy-response-ads.ts +1 -0
  430. package/src/utils/local-agent-registry.ts +474 -0
  431. package/src/utils/log-shipper.ts +126 -0
  432. package/src/utils/logger.ts +327 -0
  433. package/src/utils/markdown-renderer.tsx +1090 -0
  434. package/src/utils/math.ts +3 -0
  435. package/src/utils/message-block-helpers.ts +684 -0
  436. package/src/utils/message-history.ts +143 -0
  437. package/src/utils/message-tree-utils.ts +67 -0
  438. package/src/utils/message-updater.ts +279 -0
  439. package/src/utils/open-file.ts +146 -0
  440. package/src/utils/open-url.ts +63 -0
  441. package/src/utils/path-completion.ts +111 -0
  442. package/src/utils/path-helpers.ts +38 -0
  443. package/src/utils/pending-attachments.ts +360 -0
  444. package/src/utils/polling-backoff.ts +59 -0
  445. package/src/utils/project-picker.ts +12 -0
  446. package/src/utils/queue-panel-actions.ts +70 -0
  447. package/src/utils/recent-projects.ts +156 -0
  448. package/src/utils/renderer-cleanup.ts +233 -0
  449. package/src/utils/response-ad-positions.ts +1 -0
  450. package/src/utils/run-state-storage.ts +603 -0
  451. package/src/utils/safe-json.ts +153 -0
  452. package/src/utils/sdk-event-handlers.ts +571 -0
  453. package/src/utils/send-message-helpers.ts +210 -0
  454. package/src/utils/send-message-timer.ts +110 -0
  455. package/src/utils/settings.ts +226 -0
  456. package/src/utils/skill-registry.ts +94 -0
  457. package/src/utils/spawn-agent-matcher.ts +55 -0
  458. package/src/utils/status-indicator-state.ts +107 -0
  459. package/src/utils/stream-chunk-processor.ts +57 -0
  460. package/src/utils/strings.ts +271 -0
  461. package/src/utils/subscription.ts +31 -0
  462. package/src/utils/syntax-highlighter.tsx +19 -0
  463. package/src/utils/terminal-color-detection.ts +474 -0
  464. package/src/utils/terminal-command-broker.ts +467 -0
  465. package/src/utils/terminal-enter-detection.ts +63 -0
  466. package/src/utils/terminal-images.ts +229 -0
  467. package/src/utils/terminal-io.ts +85 -0
  468. package/src/utils/terminal-protocol-controller.ts +173 -0
  469. package/src/utils/terminal-reset-sequences.ts +33 -0
  470. package/src/utils/terminal-title.ts +84 -0
  471. package/src/utils/terminal-watchdog.ts +366 -0
  472. package/src/utils/text-layout.ts +149 -0
  473. package/src/utils/theme-config.ts +141 -0
  474. package/src/utils/theme-system.ts +1206 -0
  475. package/src/utils/think-tag-parser.ts +95 -0
  476. package/src/utils/time-format.ts +21 -0
  477. package/src/utils/trace-writer.ts +162 -0
  478. package/src/utils/ui-constants.ts +69 -0
  479. package/src/utils/update-checker.ts +110 -0
  480. package/src/utils/usage-banner-state.ts +146 -0
  481. package/src/utils/validation-error-formatting.ts +86 -0
  482. package/src/utils/validation-error-helpers.ts +19 -0
  483. package/src/utils/windows-terminal-health.ts +124 -0
  484. package/src/utils/word-wrap-utils.ts +53 -0
  485. package/src/utils/write-file-atomic.ts +54 -0
  486. package/src/utils/yield-to-event-loop.ts +9 -0
  487. package/bin/anvil.cjs +0 -40
  488. package/dist/bundled.js +0 -123450
  489. package/dist/bundled.js.map +0 -827
  490. package/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
  491. package/dist/vendor/ripgrep/arm64-linux/rg +0 -0
  492. package/dist/vendor/ripgrep/x64-darwin/rg +0 -0
  493. package/dist/vendor/ripgrep/x64-linux/rg +0 -0
  494. package/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
@@ -0,0 +1,699 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+
3
+ import type { InputMode } from '../../utils/input-modes'
4
+
5
+ // Tests cross-mode history navigation (default <-> bash mode)
6
+ // Uses mock implementation since React 19 + Bun + RTL renderHook() is unreliable
7
+
8
+ function parseHistoryItem(item: string): {
9
+ mode: InputMode
10
+ displayText: string
11
+ } {
12
+ if (item.startsWith('!') && item.length > 1) {
13
+ return { mode: 'bash', displayText: item.slice(1) }
14
+ }
15
+ return { mode: 'default', displayText: item }
16
+ }
17
+
18
+ describe('use-input-history - parseHistoryItem', () => {
19
+ describe('default mode entries', () => {
20
+ test('parses regular text as default mode', () => {
21
+ const result = parseHistoryItem('hello world')
22
+ expect(result.mode).toBe('default')
23
+ expect(result.displayText).toBe('hello world')
24
+ })
25
+
26
+ test('parses empty string as default mode', () => {
27
+ const result = parseHistoryItem('')
28
+ expect(result.mode).toBe('default')
29
+ expect(result.displayText).toBe('')
30
+ })
31
+
32
+ test('parses text with special characters as default mode', () => {
33
+ const result = parseHistoryItem('fix the bug in @file.ts')
34
+ expect(result.mode).toBe('default')
35
+ expect(result.displayText).toBe('fix the bug in @file.ts')
36
+ })
37
+
38
+ test('parses multiline text as default mode', () => {
39
+ const result = parseHistoryItem('first line\nsecond line')
40
+ expect(result.mode).toBe('default')
41
+ expect(result.displayText).toBe('first line\nsecond line')
42
+ })
43
+ })
44
+
45
+ describe('bash mode entries', () => {
46
+ test('parses !command as bash mode', () => {
47
+ const result = parseHistoryItem('!ls -la')
48
+ expect(result.mode).toBe('bash')
49
+ expect(result.displayText).toBe('ls -la')
50
+ })
51
+
52
+ test('parses !git command as bash mode', () => {
53
+ const result = parseHistoryItem('!git status')
54
+ expect(result.mode).toBe('bash')
55
+ expect(result.displayText).toBe('git status')
56
+ })
57
+
58
+ test('parses complex bash command as bash mode', () => {
59
+ const result = parseHistoryItem('!npm run test -- --watch')
60
+ expect(result.mode).toBe('bash')
61
+ expect(result.displayText).toBe('npm run test -- --watch')
62
+ })
63
+
64
+ test('parses piped bash command as bash mode', () => {
65
+ const result = parseHistoryItem('!cat file.txt | grep error')
66
+ expect(result.mode).toBe('bash')
67
+ expect(result.displayText).toBe('cat file.txt | grep error')
68
+ })
69
+ })
70
+
71
+ describe('edge cases', () => {
72
+ test('single ! is treated as default mode (not bash)', () => {
73
+ const result = parseHistoryItem('!')
74
+ expect(result.mode).toBe('default')
75
+ expect(result.displayText).toBe('!')
76
+ })
77
+
78
+ test('! in middle of text is default mode', () => {
79
+ const result = parseHistoryItem('hello! world')
80
+ expect(result.mode).toBe('default')
81
+ expect(result.displayText).toBe('hello! world')
82
+ })
83
+
84
+ test('! at end of text is default mode', () => {
85
+ const result = parseHistoryItem('hello world!')
86
+ expect(result.mode).toBe('default')
87
+ expect(result.displayText).toBe('hello world!')
88
+ })
89
+
90
+ test('!! at start is bash mode with ! prefix command', () => {
91
+ const result = parseHistoryItem('!!')
92
+ expect(result.mode).toBe('bash')
93
+ expect(result.displayText).toBe('!')
94
+ })
95
+
96
+ test('! with space is bash mode', () => {
97
+ const result = parseHistoryItem('! echo hello')
98
+ expect(result.mode).toBe('bash')
99
+ expect(result.displayText).toBe(' echo hello')
100
+ })
101
+ })
102
+ })
103
+
104
+ interface MockHistoryState {
105
+ messageHistory: string[]
106
+ historyIndex: number
107
+ currentDraft: string
108
+ currentDraftMode: InputMode
109
+ isNavigating: boolean
110
+ inputValue: string
111
+ inputMode: InputMode
112
+ }
113
+
114
+ function createMockHistoryNavigator(initialHistory: string[] = []) {
115
+ const state: MockHistoryState = {
116
+ messageHistory: initialHistory,
117
+ historyIndex: -1,
118
+ currentDraft: '',
119
+ currentDraftMode: 'default',
120
+ isNavigating: false,
121
+ inputValue: '',
122
+ inputMode: 'default',
123
+ }
124
+
125
+ const setInputValue = (value: { text: string; cursorPosition: number; lastEditDueToNav: boolean }) => {
126
+ state.inputValue = value.text
127
+ }
128
+
129
+ const setInputMode = (mode: InputMode) => {
130
+ state.inputMode = mode
131
+ }
132
+
133
+ const resetHistoryNavigation = () => {
134
+ state.historyIndex = -1
135
+ state.currentDraft = ''
136
+ state.currentDraftMode = 'default'
137
+ }
138
+
139
+ const navigateUp = () => {
140
+ const history = state.messageHistory
141
+ if (history.length === 0) return
142
+
143
+ state.isNavigating = true
144
+
145
+ if (state.historyIndex === -1) {
146
+ state.currentDraft = state.inputMode === 'bash' ? '!' + state.inputValue : state.inputValue
147
+ state.currentDraftMode = state.inputMode
148
+ state.historyIndex = history.length - 1
149
+ } else if (state.historyIndex > 0) {
150
+ state.historyIndex -= 1
151
+ }
152
+
153
+ const historyMessage = history[state.historyIndex]
154
+ if (historyMessage === undefined) {
155
+ state.isNavigating = false
156
+ return
157
+ }
158
+
159
+ const { mode, displayText } = parseHistoryItem(historyMessage)
160
+
161
+ if (mode !== state.inputMode) {
162
+ setInputMode(mode)
163
+ }
164
+
165
+ setInputValue({
166
+ text: displayText,
167
+ cursorPosition: displayText.length,
168
+ lastEditDueToNav: true,
169
+ })
170
+
171
+ state.isNavigating = false
172
+ }
173
+
174
+ const navigateDown = () => {
175
+ const history = state.messageHistory
176
+ if (history.length === 0) return
177
+ if (state.historyIndex === -1) return
178
+
179
+ state.isNavigating = true
180
+
181
+ if (state.historyIndex < history.length - 1) {
182
+ state.historyIndex += 1
183
+ const historyMessage = history[state.historyIndex]
184
+ if (historyMessage === undefined) {
185
+ state.isNavigating = false
186
+ return
187
+ }
188
+
189
+ const { mode, displayText } = parseHistoryItem(historyMessage)
190
+
191
+ // Switch mode if needed
192
+ if (mode !== state.inputMode) {
193
+ setInputMode(mode)
194
+ }
195
+
196
+ setInputValue({
197
+ text: displayText,
198
+ cursorPosition: displayText.length,
199
+ lastEditDueToNav: true,
200
+ })
201
+ } else {
202
+ state.historyIndex = -1
203
+ const draft = state.currentDraft
204
+ const draftMode = state.currentDraftMode
205
+
206
+ if (draftMode !== state.inputMode) {
207
+ setInputMode(draftMode)
208
+ }
209
+
210
+ const textToShow =
211
+ draftMode === 'bash' && draft.startsWith('!') ? draft.slice(1) : draft
212
+
213
+ setInputValue({
214
+ text: textToShow,
215
+ cursorPosition: textToShow.length,
216
+ lastEditDueToNav: true,
217
+ })
218
+ }
219
+
220
+ state.isNavigating = false
221
+ }
222
+
223
+ const simulateInputModeChange = (newMode: InputMode) => {
224
+ const oldMode = state.inputMode
225
+ state.inputMode = newMode
226
+
227
+ if (!state.isNavigating && oldMode !== newMode) {
228
+ resetHistoryNavigation()
229
+ }
230
+ }
231
+
232
+ return {
233
+ state,
234
+ setInputValue,
235
+ setInputMode,
236
+ resetHistoryNavigation,
237
+ navigateUp,
238
+ navigateDown,
239
+ simulateInputModeChange,
240
+ }
241
+ }
242
+
243
+ describe('use-input-history - cross-mode navigation', () => {
244
+ describe('navigating from default mode to bash entries', () => {
245
+ test('navigating up to a bash entry switches to bash mode', () => {
246
+ const nav = createMockHistoryNavigator(['hello world', '!ls -la'])
247
+
248
+ expect(nav.state.inputMode).toBe('default')
249
+ nav.navigateUp()
250
+
251
+ expect(nav.state.inputMode).toBe('bash')
252
+ expect(nav.state.inputValue).toBe('ls -la')
253
+ expect(nav.state.historyIndex).toBe(1)
254
+ })
255
+
256
+ test('navigating up through mixed history changes modes appropriately', () => {
257
+ const nav = createMockHistoryNavigator([
258
+ 'default entry 1',
259
+ '!bash command 1',
260
+ 'default entry 2',
261
+ '!bash command 2',
262
+ ])
263
+
264
+ nav.navigateUp()
265
+ expect(nav.state.inputMode).toBe('bash')
266
+ expect(nav.state.inputValue).toBe('bash command 2')
267
+
268
+ nav.navigateUp()
269
+ expect(nav.state.inputMode).toBe('default')
270
+ expect(nav.state.inputValue).toBe('default entry 2')
271
+
272
+ nav.navigateUp()
273
+ expect(nav.state.inputMode).toBe('bash')
274
+ expect(nav.state.inputValue).toBe('bash command 1')
275
+
276
+ nav.navigateUp()
277
+ expect(nav.state.inputMode).toBe('default')
278
+ expect(nav.state.inputValue).toBe('default entry 1')
279
+ })
280
+ })
281
+
282
+ describe('navigating from bash mode to default entries', () => {
283
+ test('navigating up from bash mode to a default entry switches to default mode', () => {
284
+ const nav = createMockHistoryNavigator(['hello world', '!ls -la'])
285
+
286
+ nav.state.inputMode = 'bash'
287
+ nav.state.inputValue = 'pwd'
288
+
289
+ nav.navigateUp()
290
+ expect(nav.state.inputMode as string).toBe('bash')
291
+ expect(nav.state.inputValue).toBe('ls -la')
292
+
293
+ nav.navigateUp()
294
+ expect(nav.state.inputMode as string).toBe('default')
295
+ expect(nav.state.inputValue).toBe('hello world')
296
+ })
297
+ })
298
+
299
+ describe('returning to draft restores original mode', () => {
300
+ test('navigating back to draft restores default mode', () => {
301
+ const nav = createMockHistoryNavigator(['!bash command'])
302
+
303
+ nav.state.inputMode = 'default'
304
+ nav.state.inputValue = 'my draft text'
305
+
306
+ nav.navigateUp()
307
+ expect(nav.state.inputMode as string).toBe('bash')
308
+ expect(nav.state.inputValue).toBe('bash command')
309
+
310
+ nav.navigateDown()
311
+ expect(nav.state.inputMode as string).toBe('default')
312
+ expect(nav.state.inputValue).toBe('my draft text')
313
+ })
314
+
315
+ test('navigating back to draft restores bash mode', () => {
316
+ const nav = createMockHistoryNavigator(['default entry'])
317
+
318
+ nav.state.inputMode = 'bash'
319
+ nav.state.inputValue = 'my bash draft'
320
+
321
+ nav.navigateUp()
322
+ expect(nav.state.inputMode as string).toBe('default')
323
+ expect(nav.state.inputValue).toBe('default entry')
324
+
325
+ nav.navigateDown()
326
+ expect(nav.state.inputMode as string).toBe('bash')
327
+ expect(nav.state.inputValue).toBe('my bash draft')
328
+ })
329
+
330
+ test('draft is preserved with ! prefix for bash mode', () => {
331
+ const nav = createMockHistoryNavigator(['default entry'])
332
+
333
+ nav.state.inputMode = 'bash'
334
+ nav.state.inputValue = 'git status'
335
+
336
+ nav.navigateUp()
337
+ expect(nav.state.currentDraft).toBe('!git status')
338
+ expect(nav.state.currentDraftMode).toBe('bash')
339
+
340
+ nav.navigateDown()
341
+ expect(nav.state.inputValue).toBe('git status')
342
+ expect(nav.state.inputMode as string).toBe('bash')
343
+ })
344
+ })
345
+
346
+ describe('navigation through entire history', () => {
347
+ test('can navigate up through all entries and back down to draft', () => {
348
+ const nav = createMockHistoryNavigator([
349
+ 'first',
350
+ '!second',
351
+ 'third',
352
+ ])
353
+
354
+ nav.state.inputValue = 'draft'
355
+ nav.state.inputMode = 'default'
356
+
357
+ // Navigate up through all entries
358
+ nav.navigateUp()
359
+ expect(nav.state.inputValue).toBe('third')
360
+ expect(nav.state.inputMode).toBe('default')
361
+
362
+ nav.navigateUp()
363
+ expect(nav.state.inputValue).toBe('second')
364
+ expect(nav.state.inputMode as string).toBe('bash')
365
+
366
+ nav.navigateUp()
367
+ expect(nav.state.inputValue).toBe('first')
368
+ expect(nav.state.inputMode).toBe('default')
369
+
370
+ // Should stay at oldest entry
371
+ nav.navigateUp()
372
+ expect(nav.state.inputValue).toBe('first')
373
+ expect(nav.state.historyIndex).toBe(0)
374
+
375
+ // Navigate back down
376
+ nav.navigateDown()
377
+ expect(nav.state.inputValue).toBe('second')
378
+ expect(nav.state.inputMode as string).toBe('bash')
379
+
380
+ nav.navigateDown()
381
+ expect(nav.state.inputValue).toBe('third')
382
+ expect(nav.state.inputMode).toBe('default')
383
+
384
+ nav.navigateDown()
385
+ expect(nav.state.inputValue).toBe('draft')
386
+ expect(nav.state.inputMode).toBe('default')
387
+
388
+ // Should stay at draft
389
+ nav.navigateDown()
390
+ expect(nav.state.inputValue).toBe('draft')
391
+ expect(nav.state.historyIndex).toBe(-1)
392
+ })
393
+ })
394
+ })
395
+
396
+ describe('use-input-history - isNavigating flag behavior', () => {
397
+ describe('navigation sets and clears isNavigating flag', () => {
398
+ test('navigateUp sets isNavigating during mode change', () => {
399
+ const nav = createMockHistoryNavigator(['!bash command'])
400
+
401
+ nav.state.inputMode = 'default'
402
+ expect(nav.state.isNavigating).toBe(false)
403
+
404
+ nav.navigateUp()
405
+ expect(nav.state.isNavigating).toBe(false)
406
+ expect(nav.state.inputMode as string).toBe('bash')
407
+ })
408
+
409
+ test('navigateDown sets isNavigating during mode change', () => {
410
+ const nav = createMockHistoryNavigator(['default entry', '!bash command'])
411
+
412
+ nav.navigateUp()
413
+ expect(nav.state.inputMode).toBe('bash')
414
+
415
+ nav.navigateDown()
416
+ expect(nav.state.inputMode).toBe('default')
417
+ expect(nav.state.isNavigating).toBe(false)
418
+ })
419
+ })
420
+
421
+ describe('useEffect reset is prevented during navigation', () => {
422
+ test('manual mode change resets history navigation', () => {
423
+ const nav = createMockHistoryNavigator(['entry 1', 'entry 2'])
424
+
425
+ nav.navigateUp()
426
+ expect(nav.state.historyIndex).toBe(1)
427
+ expect(nav.state.inputValue).toBe('entry 2')
428
+
429
+ nav.simulateInputModeChange('bash')
430
+ expect(nav.state.historyIndex).toBe(-1)
431
+ expect(nav.state.currentDraft).toBe('')
432
+ expect(nav.state.currentDraftMode).toBe('default')
433
+ })
434
+
435
+ test('mode change during navigation does NOT reset history', () => {
436
+ const nav = createMockHistoryNavigator(['default entry', '!bash command'])
437
+
438
+ nav.state.isNavigating = true
439
+ nav.simulateInputModeChange('bash')
440
+ nav.state.historyIndex = 1
441
+ nav.simulateInputModeChange('default')
442
+ nav.state.isNavigating = false
443
+ })
444
+
445
+ test('exiting feedback mode explicitly resets history navigation', () => {
446
+ const nav = createMockHistoryNavigator(['entry 1', 'entry 2'])
447
+
448
+ nav.navigateUp()
449
+ expect(nav.state.historyIndex).toBe(1)
450
+
451
+ nav.resetHistoryNavigation()
452
+
453
+ expect(nav.state.historyIndex).toBe(-1)
454
+ expect(nav.state.currentDraft).toBe('')
455
+ expect(nav.state.currentDraftMode).toBe('default')
456
+ })
457
+ })
458
+ })
459
+
460
+ describe('use-input-history - resetHistoryNavigation', () => {
461
+ test('resets historyIndex to -1', () => {
462
+ const nav = createMockHistoryNavigator(['entry'])
463
+
464
+ nav.navigateUp()
465
+ expect(nav.state.historyIndex).toBe(0)
466
+
467
+ nav.resetHistoryNavigation()
468
+ expect(nav.state.historyIndex).toBe(-1)
469
+ })
470
+
471
+ test('resets currentDraft to empty string', () => {
472
+ const nav = createMockHistoryNavigator(['entry'])
473
+ nav.state.inputValue = 'my draft'
474
+
475
+ nav.navigateUp()
476
+ expect(nav.state.currentDraft).toBe('my draft')
477
+
478
+ nav.resetHistoryNavigation()
479
+ expect(nav.state.currentDraft).toBe('')
480
+ })
481
+
482
+ test('resets currentDraftMode to default', () => {
483
+ const nav = createMockHistoryNavigator(['entry'])
484
+ nav.state.inputMode = 'bash'
485
+ nav.state.inputValue = 'my bash draft'
486
+
487
+ nav.navigateUp()
488
+ expect(nav.state.currentDraftMode).toBe('bash')
489
+
490
+ nav.resetHistoryNavigation()
491
+ expect(nav.state.currentDraftMode).toBe('default')
492
+ })
493
+
494
+ test('can be called multiple times safely', () => {
495
+ const nav = createMockHistoryNavigator(['entry'])
496
+
497
+ nav.resetHistoryNavigation()
498
+ nav.resetHistoryNavigation()
499
+ nav.resetHistoryNavigation()
500
+
501
+ expect(nav.state.historyIndex).toBe(-1)
502
+ expect(nav.state.currentDraft).toBe('')
503
+ expect(nav.state.currentDraftMode).toBe('default')
504
+ })
505
+
506
+ test('allows navigation after reset', () => {
507
+ const nav = createMockHistoryNavigator(['entry 1', 'entry 2'])
508
+
509
+ nav.navigateUp()
510
+ expect(nav.state.inputValue).toBe('entry 2')
511
+
512
+ nav.resetHistoryNavigation()
513
+
514
+ nav.navigateUp()
515
+ expect(nav.state.inputValue).toBe('entry 2')
516
+ expect(nav.state.historyIndex).toBe(1)
517
+ })
518
+ })
519
+
520
+ describe('use-input-history - edge cases', () => {
521
+ describe('empty history', () => {
522
+ test('navigateUp does nothing with empty history', () => {
523
+ const nav = createMockHistoryNavigator([])
524
+
525
+ nav.state.inputValue = 'current text'
526
+ nav.navigateUp()
527
+
528
+ expect(nav.state.inputValue).toBe('current text')
529
+ expect(nav.state.historyIndex).toBe(-1)
530
+ })
531
+
532
+ test('navigateDown does nothing with empty history', () => {
533
+ const nav = createMockHistoryNavigator([])
534
+
535
+ nav.state.inputValue = 'current text'
536
+ nav.navigateDown()
537
+
538
+ expect(nav.state.inputValue).toBe('current text')
539
+ expect(nav.state.historyIndex).toBe(-1)
540
+ })
541
+ })
542
+
543
+ describe('single entry history', () => {
544
+ test('can navigate up and down with single entry', () => {
545
+ const nav = createMockHistoryNavigator(['only entry'])
546
+ nav.state.inputValue = 'draft'
547
+
548
+ nav.navigateUp()
549
+ expect(nav.state.inputValue).toBe('only entry')
550
+ expect(nav.state.historyIndex).toBe(0)
551
+
552
+ nav.navigateUp()
553
+ expect(nav.state.inputValue).toBe('only entry')
554
+ expect(nav.state.historyIndex).toBe(0)
555
+
556
+ nav.navigateDown()
557
+ expect(nav.state.inputValue).toBe('draft')
558
+ expect(nav.state.historyIndex).toBe(-1)
559
+ })
560
+ })
561
+
562
+ describe('navigateDown without prior navigateUp', () => {
563
+ test('navigateDown at draft does nothing', () => {
564
+ const nav = createMockHistoryNavigator(['entry 1', 'entry 2'])
565
+
566
+ nav.state.inputValue = 'draft'
567
+ nav.navigateDown()
568
+
569
+ expect(nav.state.inputValue).toBe('draft')
570
+ expect(nav.state.historyIndex).toBe(-1)
571
+ })
572
+ })
573
+
574
+ describe('rapid navigation', () => {
575
+ test('rapid up/down navigation works correctly', () => {
576
+ const nav = createMockHistoryNavigator(['a', 'b', 'c'])
577
+ nav.state.inputValue = 'draft'
578
+
579
+ nav.navigateUp() // c
580
+ nav.navigateUp() // b
581
+ nav.navigateDown() // c
582
+ nav.navigateUp() // b
583
+ nav.navigateUp() // a
584
+ nav.navigateDown() // b
585
+ nav.navigateDown() // c
586
+ nav.navigateDown() // draft
587
+
588
+ expect(nav.state.inputValue).toBe('draft')
589
+ expect(nav.state.historyIndex).toBe(-1)
590
+ })
591
+ })
592
+
593
+ describe('special characters in history', () => {
594
+ test('handles entries with special characters', () => {
595
+ const nav = createMockHistoryNavigator([
596
+ 'entry with @mention',
597
+ '!command with "quotes"',
598
+ 'entry with \nnewline',
599
+ ])
600
+
601
+ nav.navigateUp()
602
+ expect(nav.state.inputValue).toBe('entry with \nnewline')
603
+
604
+ nav.navigateUp()
605
+ expect(nav.state.inputValue).toBe('command with "quotes"')
606
+ expect(nav.state.inputMode).toBe('bash')
607
+
608
+ nav.navigateUp()
609
+ expect(nav.state.inputValue).toBe('entry with @mention')
610
+ expect(nav.state.inputMode).toBe('default')
611
+ })
612
+ })
613
+
614
+ describe('unicode in history', () => {
615
+ test('handles unicode characters in entries', () => {
616
+ const nav = createMockHistoryNavigator([
617
+ '日本語のテキスト',
618
+ '!echo 🚀',
619
+ 'émojis 👍 and açcénts',
620
+ ])
621
+
622
+ nav.navigateUp()
623
+ expect(nav.state.inputValue).toBe('émojis 👍 and açcénts')
624
+
625
+ nav.navigateUp()
626
+ expect(nav.state.inputValue).toBe('echo 🚀')
627
+ expect(nav.state.inputMode).toBe('bash')
628
+
629
+ nav.navigateUp()
630
+ expect(nav.state.inputValue).toBe('日本語のテキスト')
631
+ expect(nav.state.inputMode).toBe('default')
632
+ })
633
+ })
634
+
635
+ describe('very long entries', () => {
636
+ test('handles very long history entries', () => {
637
+ const longText = 'a'.repeat(10000)
638
+ const longBashCommand = '!' + 'b'.repeat(10000)
639
+
640
+ const nav = createMockHistoryNavigator([longText, longBashCommand])
641
+
642
+ nav.navigateUp()
643
+ expect(nav.state.inputValue).toBe('b'.repeat(10000))
644
+ expect(nav.state.inputMode).toBe('bash')
645
+
646
+ nav.navigateUp()
647
+ expect(nav.state.inputValue).toBe(longText)
648
+ expect(nav.state.inputMode).toBe('default')
649
+ })
650
+ })
651
+ })
652
+
653
+ describe('use-input-history - mode preservation', () => {
654
+ test('preserves draft mode when navigating and returning', () => {
655
+ const nav = createMockHistoryNavigator([
656
+ 'default 1',
657
+ '!bash 1',
658
+ 'default 2',
659
+ '!bash 2',
660
+ ])
661
+
662
+ nav.state.inputMode = 'default'
663
+ nav.state.inputValue = 'my default draft'
664
+
665
+ nav.navigateUp()
666
+ nav.navigateUp()
667
+ nav.navigateUp()
668
+ nav.navigateUp()
669
+
670
+ nav.navigateDown()
671
+ nav.navigateDown()
672
+ nav.navigateDown()
673
+ nav.navigateDown()
674
+ expect(nav.state.inputMode).toBe('default')
675
+ expect(nav.state.inputValue).toBe('my default draft')
676
+ })
677
+
678
+ test('preserves bash mode draft when navigating through default entries', () => {
679
+ const nav = createMockHistoryNavigator(['default 1', 'default 2', 'default 3'])
680
+
681
+ nav.state.inputMode = 'bash'
682
+ nav.state.inputValue = 'npm test'
683
+
684
+ nav.navigateUp()
685
+ expect(nav.state.inputMode as string).toBe('default')
686
+
687
+ nav.navigateUp()
688
+ expect(nav.state.inputMode as string).toBe('default')
689
+
690
+ nav.navigateUp()
691
+ expect(nav.state.inputMode as string).toBe('default')
692
+
693
+ nav.navigateDown()
694
+ nav.navigateDown()
695
+ nav.navigateDown()
696
+ expect(nav.state.inputMode).toBe('bash')
697
+ expect(nav.state.inputValue).toBe('npm test')
698
+ })
699
+ })