@alumbwe/anvil 1.0.41 → 1.0.49

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 (241) hide show
  1. package/bin/anvil.js +18 -0
  2. package/package.json +13 -6
  3. package/src/agents/bundled-agents.generated.ts +1 -1
  4. package/src/entry.ts +5 -0
  5. package/src/index.tsx +27 -4
  6. package/src/login/plain-login.ts +60 -0
  7. package/src/login/supabase-auth.ts +346 -0
  8. package/src/pre-init/client-env.ts +17 -0
  9. package/src/utils/anvil-client.ts +4 -0
  10. package/src/utils/auth.ts +16 -4
  11. package/src/utils/client-env-defaults.ts +28 -0
  12. package/src/utils/config-dir.ts +1 -1
  13. package/vendor/@anvil/common/src/constants/deepseek-direct.ts +30 -1
  14. package/vendor/@anvil/common/src/util/project-ignore.ts +4 -3
  15. package/vendor/@anvil/sdk/dist/index.cjs +46 -10
  16. package/vendor/@anvil/sdk/dist/index.mjs +46 -10
  17. package/src/commands/__tests__/anvil-command-aliases.test.ts +0 -56
  18. package/src/commands/__tests__/bash-command.test.ts +0 -445
  19. package/src/commands/__tests__/command-args.test.ts +0 -349
  20. package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
  21. package/src/commands/__tests__/copy-conversation.test.ts +0 -226
  22. package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
  23. package/src/commands/__tests__/image.test.ts +0 -99
  24. package/src/commands/__tests__/init.test.ts +0 -456
  25. package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
  26. package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
  27. package/src/commands/__tests__/prompt-builders.test.ts +0 -28
  28. package/src/commands/__tests__/router-input.test.ts +0 -298
  29. package/src/components/__tests__/ad-banner.test.tsx +0 -83
  30. package/src/components/__tests__/anvil-landing-heading-row.test.tsx +0 -91
  31. package/src/components/__tests__/anvil-model-selector.test.tsx +0 -519
  32. package/src/components/__tests__/anvil-offer-invariants.test.ts +0 -54
  33. package/src/components/__tests__/anvil-takeover-prompt.test.tsx +0 -212
  34. package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
  35. package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
  36. package/src/components/__tests__/grid-layout.test.tsx +0 -1051
  37. package/src/components/__tests__/message-block.completion.test.tsx +0 -104
  38. package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
  39. package/src/components/__tests__/message-with-agents.test.tsx +0 -579
  40. package/src/components/__tests__/multiline-input.test.tsx +0 -1132
  41. package/src/components/__tests__/queue-panel.test.tsx +0 -398
  42. package/src/components/__tests__/selectable-list.test.ts +0 -232
  43. package/src/components/__tests__/status-bar.test.tsx +0 -44
  44. package/src/components/__tests__/status-indicator.test.tsx +0 -204
  45. package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
  46. package/src/components/__tests__/user-error-banner.test.tsx +0 -102
  47. package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
  48. package/src/components/ask-user/__tests__/validation.test.ts +0 -213
  49. package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
  50. package/src/components/tools/__tests__/code-search.test.tsx +0 -45
  51. package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
  52. package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
  53. package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
  54. package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
  55. package/src/data/slash-commands.test.ts +0 -24
  56. package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +0 -49
  57. package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
  58. package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
  59. package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
  60. package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
  61. package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
  62. package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
  63. package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
  64. package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
  65. package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
  66. package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
  67. package/src/hooks/__tests__/use-input-history.test.ts +0 -699
  68. package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
  69. package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
  70. package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
  71. package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
  72. package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
  73. package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
  74. package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
  75. package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
  76. package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
  77. package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
  78. package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
  79. package/src/hooks/__tests__/use-timeout.test.ts +0 -330
  80. package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
  81. package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
  82. package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
  83. package/src/init/__tests__/init-direnv.test.ts +0 -526
  84. package/src/state/__tests__/chat-store-focus.test.ts +0 -22
  85. package/src/state/__tests__/feedback-store.test.ts +0 -257
  86. package/src/utils/__tests__/active-run.test.ts +0 -140
  87. package/src/utils/__tests__/activity-tracker.test.ts +0 -440
  88. package/src/utils/__tests__/agent-display.test.ts +0 -139
  89. package/src/utils/__tests__/analytics-client.test.ts +0 -262
  90. package/src/utils/__tests__/anonymous-id.test.ts +0 -74
  91. package/src/utils/__tests__/anvil-api.test.ts +0 -578
  92. package/src/utils/__tests__/anvil-instance-owner.test.ts +0 -69
  93. package/src/utils/__tests__/anvil-model-navigation.test.ts +0 -102
  94. package/src/utils/__tests__/anvil-premium-reset.test.ts +0 -79
  95. package/src/utils/__tests__/anvil-referral-cache.test.ts +0 -116
  96. package/src/utils/__tests__/anvil-session-api.test.ts +0 -215
  97. package/src/utils/__tests__/anvil-session-display.test.ts +0 -23
  98. package/src/utils/__tests__/anvil-streak-line.test.ts +0 -217
  99. package/src/utils/__tests__/arrays.test.ts +0 -264
  100. package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
  101. package/src/utils/__tests__/block-processor.test.ts +0 -810
  102. package/src/utils/__tests__/chat-history.test.ts +0 -203
  103. package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
  104. package/src/utils/__tests__/chat-meta.test.ts +0 -130
  105. package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
  106. package/src/utils/__tests__/clipboard.test.ts +0 -910
  107. package/src/utils/__tests__/code-search-summary.test.ts +0 -84
  108. package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
  109. package/src/utils/__tests__/error-handling.test.ts +0 -658
  110. package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
  111. package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
  112. package/src/utils/__tests__/feedback-submission.test.ts +0 -26
  113. package/src/utils/__tests__/fetch-usage.test.ts +0 -355
  114. package/src/utils/__tests__/fingerprint.test.ts +0 -144
  115. package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
  116. package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
  117. package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
  118. package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
  119. package/src/utils/__tests__/format-timeout.test.ts +0 -87
  120. package/src/utils/__tests__/image-dimensions.test.ts +0 -249
  121. package/src/utils/__tests__/image-processor.test.ts +0 -209
  122. package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
  123. package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
  124. package/src/utils/__tests__/layout-helpers.test.ts +0 -36
  125. package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
  126. package/src/utils/__tests__/log-shipper.test.ts +0 -53
  127. package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
  128. package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
  129. package/src/utils/__tests__/message-updater.test.ts +0 -645
  130. package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
  131. package/src/utils/__tests__/pending-attachments.test.ts +0 -277
  132. package/src/utils/__tests__/polling-backoff.test.ts +0 -51
  133. package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
  134. package/src/utils/__tests__/run-state-storage.test.ts +0 -919
  135. package/src/utils/__tests__/safe-json.test.ts +0 -77
  136. package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
  137. package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
  138. package/src/utils/__tests__/send-message-timer.test.ts +0 -64
  139. package/src/utils/__tests__/settings.test.ts +0 -99
  140. package/src/utils/__tests__/strings.test.ts +0 -192
  141. package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
  142. package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
  143. package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
  144. package/src/utils/__tests__/terminal-io.test.ts +0 -137
  145. package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
  146. package/src/utils/__tests__/text-layout.test.ts +0 -82
  147. package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
  148. package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
  149. package/src/utils/__tests__/trace-writer.test.ts +0 -223
  150. package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
  151. package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
  152. package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
  153. package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
  154. package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
  155. package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +0 -846
  156. package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +0 -101
  157. package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +0 -1408
  158. package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +0 -258
  159. package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +0 -48
  160. package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +0 -282
  161. package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +0 -526
  162. package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +0 -68
  163. package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +0 -132
  164. package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +0 -422
  165. package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +0 -167
  166. package/vendor/@anvil/common/src/__tests__/env-process.test.ts +0 -145
  167. package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +0 -234
  168. package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +0 -379
  169. package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +0 -807
  170. package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +0 -154
  171. package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +0 -246
  172. package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +0 -77
  173. package/vendor/@anvil/common/src/__tests__/model-config.test.ts +0 -89
  174. package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +0 -298
  175. package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +0 -116
  176. package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +0 -167
  177. package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +0 -132
  178. package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +0 -72
  179. package/vendor/@anvil/common/src/__tests__/user-state.test.ts +0 -30
  180. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +0 -63
  181. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +0 -246
  182. package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +0 -106
  183. package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +0 -351
  184. package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +0 -169
  185. package/vendor/@anvil/common/src/testing/errors.ts +0 -33
  186. package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +0 -334
  187. package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +0 -6
  188. package/vendor/@anvil/common/src/testing/index.ts +0 -84
  189. package/vendor/@anvil/common/src/testing/mock-modules.ts +0 -53
  190. package/vendor/@anvil/common/src/testing/mock-types.ts +0 -123
  191. package/vendor/@anvil/common/src/testing/mocks/analytics.ts +0 -261
  192. package/vendor/@anvil/common/src/testing/mocks/child-process.ts +0 -93
  193. package/vendor/@anvil/common/src/testing/mocks/crypto.ts +0 -218
  194. package/vendor/@anvil/common/src/testing/mocks/database.ts +0 -337
  195. package/vendor/@anvil/common/src/testing/mocks/fetch.ts +0 -219
  196. package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +0 -166
  197. package/vendor/@anvil/common/src/testing/mocks/index.ts +0 -101
  198. package/vendor/@anvil/common/src/testing/mocks/logger.ts +0 -135
  199. package/vendor/@anvil/common/src/testing/mocks/stream.ts +0 -313
  200. package/vendor/@anvil/common/src/testing/mocks/timers.ts +0 -132
  201. package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +0 -127
  202. package/vendor/@anvil/common/src/testing/setup.ts +0 -282
  203. package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +0 -34
  204. package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +0 -213
  205. package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +0 -47
  206. package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +0 -65
  207. package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +0 -49
  208. package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +0 -20
  209. package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +0 -26
  210. package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +0 -122
  211. package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +0 -102
  212. package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +0 -143
  213. package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +0 -202
  214. package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +0 -193
  215. package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +0 -140
  216. package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +0 -148
  217. package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +0 -115
  218. package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +0 -57
  219. package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +0 -774
  220. package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +0 -206
  221. package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +0 -173
  222. package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +0 -60
  223. package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +0 -31
  224. package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +0 -1252
  225. package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +0 -505
  226. package/vendor/@anvil/common/src/util/__tests__/path.test.ts +0 -22
  227. package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +0 -90
  228. package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +0 -325
  229. package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +0 -28
  230. package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +0 -130
  231. package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +0 -1008
  232. package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +0 -289
  233. package/vendor/@anvil/common/src/util/__tests__/string.test.ts +0 -239
  234. package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +0 -70
  235. package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +0 -108
  236. package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +0 -180
  237. package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +0 -24
  238. package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +0 -88
  239. package/vendor/@anvil/common/src/util/skills.test.ts +0 -44
  240. package/vendor/@anvil/sdk/dist/index.cjs.map +0 -440
  241. package/vendor/@anvil/sdk/dist/index.mjs.map +0 -440
@@ -1,699 +0,0 @@
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
- })