@prestyj/cli 4.6.4 → 4.10.1

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 (316) hide show
  1. package/dist/cli/auth.d.ts.map +1 -1
  2. package/dist/cli/auth.js +11 -10
  3. package/dist/cli/auth.js.map +1 -1
  4. package/dist/cli/mcp.js +1 -1
  5. package/dist/cli/shared.d.ts +12 -0
  6. package/dist/cli/shared.d.ts.map +1 -1
  7. package/dist/cli/shared.js +31 -6
  8. package/dist/cli/shared.js.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +63 -62
  11. package/dist/cli.js.map +1 -1
  12. package/dist/config.d.ts +7 -14
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +13 -17
  15. package/dist/config.js.map +1 -1
  16. package/dist/core/agent-session-compaction.test.js +1 -1
  17. package/dist/core/agent-session-compaction.test.js.map +1 -1
  18. package/dist/core/agent-session.d.ts +1 -0
  19. package/dist/core/agent-session.d.ts.map +1 -1
  20. package/dist/core/agent-session.js +6 -2
  21. package/dist/core/agent-session.js.map +1 -1
  22. package/dist/core/auth-storage.d.ts +1 -58
  23. package/dist/core/auth-storage.d.ts.map +1 -1
  24. package/dist/core/auth-storage.js +3 -254
  25. package/dist/core/auth-storage.js.map +1 -1
  26. package/dist/core/auto-update.d.ts +4 -26
  27. package/dist/core/auto-update.d.ts.map +1 -1
  28. package/dist/core/auto-update.js +12 -237
  29. package/dist/core/auto-update.js.map +1 -1
  30. package/dist/core/claude-code-version.d.ts +1 -9
  31. package/dist/core/claude-code-version.d.ts.map +1 -1
  32. package/dist/core/claude-code-version.js +2 -105
  33. package/dist/core/claude-code-version.js.map +1 -1
  34. package/dist/core/compaction/compactor.test.js +2 -2
  35. package/dist/core/compaction/compactor.test.js.map +1 -1
  36. package/dist/core/file-lock.d.ts +1 -5
  37. package/dist/core/file-lock.d.ts.map +1 -1
  38. package/dist/core/file-lock.js +2 -75
  39. package/dist/core/file-lock.js.map +1 -1
  40. package/dist/core/logger.d.ts +4 -17
  41. package/dist/core/logger.d.ts.map +1 -1
  42. package/dist/core/logger.js +21 -110
  43. package/dist/core/logger.js.map +1 -1
  44. package/dist/core/lsp/client.d.ts +63 -0
  45. package/dist/core/lsp/client.d.ts.map +1 -0
  46. package/dist/core/lsp/client.js +215 -0
  47. package/dist/core/lsp/client.js.map +1 -0
  48. package/dist/core/lsp/format.d.ts +12 -0
  49. package/dist/core/lsp/format.d.ts.map +1 -0
  50. package/dist/core/lsp/format.js +30 -0
  51. package/dist/core/lsp/format.js.map +1 -0
  52. package/dist/core/lsp/format.test.d.ts +2 -0
  53. package/dist/core/lsp/format.test.d.ts.map +1 -0
  54. package/dist/core/lsp/format.test.js +56 -0
  55. package/dist/core/lsp/format.test.js.map +1 -0
  56. package/dist/core/lsp/integration.test.d.ts +2 -0
  57. package/dist/core/lsp/integration.test.d.ts.map +1 -0
  58. package/dist/core/lsp/integration.test.js +60 -0
  59. package/dist/core/lsp/integration.test.js.map +1 -0
  60. package/dist/core/lsp/jsonrpc.d.ts +32 -0
  61. package/dist/core/lsp/jsonrpc.d.ts.map +1 -0
  62. package/dist/core/lsp/jsonrpc.js +141 -0
  63. package/dist/core/lsp/jsonrpc.js.map +1 -0
  64. package/dist/core/lsp/jsonrpc.test.d.ts +2 -0
  65. package/dist/core/lsp/jsonrpc.test.d.ts.map +1 -0
  66. package/dist/core/lsp/jsonrpc.test.js +127 -0
  67. package/dist/core/lsp/jsonrpc.test.js.map +1 -0
  68. package/dist/core/lsp/manager.d.ts +40 -0
  69. package/dist/core/lsp/manager.d.ts.map +1 -0
  70. package/dist/core/lsp/manager.js +135 -0
  71. package/dist/core/lsp/manager.js.map +1 -0
  72. package/dist/core/lsp/manager.test.d.ts +2 -0
  73. package/dist/core/lsp/manager.test.d.ts.map +1 -0
  74. package/dist/core/lsp/manager.test.js +119 -0
  75. package/dist/core/lsp/manager.test.js.map +1 -0
  76. package/dist/core/lsp/servers.d.ts +45 -0
  77. package/dist/core/lsp/servers.d.ts.map +1 -0
  78. package/dist/core/lsp/servers.js +247 -0
  79. package/dist/core/lsp/servers.js.map +1 -0
  80. package/dist/core/mcp/defaults.d.ts +1 -1
  81. package/dist/core/mcp/defaults.js +1 -1
  82. package/dist/core/mcp/store.d.ts +1 -1
  83. package/dist/core/mcp/store.test.js +1 -1
  84. package/dist/core/mcp/store.test.js.map +1 -1
  85. package/dist/core/model-registry.d.ts +1 -75
  86. package/dist/core/model-registry.d.ts.map +1 -1
  87. package/dist/core/model-registry.js +4 -344
  88. package/dist/core/model-registry.js.map +1 -1
  89. package/dist/core/oauth/anthropic.d.ts +1 -3
  90. package/dist/core/oauth/anthropic.d.ts.map +1 -1
  91. package/dist/core/oauth/anthropic.js +2 -96
  92. package/dist/core/oauth/anthropic.js.map +1 -1
  93. package/dist/core/oauth/gemini.d.ts +1 -3
  94. package/dist/core/oauth/gemini.d.ts.map +1 -1
  95. package/dist/core/oauth/gemini.js +2 -385
  96. package/dist/core/oauth/gemini.js.map +1 -1
  97. package/dist/core/oauth/kimi.d.ts +1 -45
  98. package/dist/core/oauth/kimi.d.ts.map +1 -1
  99. package/dist/core/oauth/kimi.js +2 -277
  100. package/dist/core/oauth/kimi.js.map +1 -1
  101. package/dist/core/oauth/openai.d.ts +1 -3
  102. package/dist/core/oauth/openai.d.ts.map +1 -1
  103. package/dist/core/oauth/openai.js +2 -187
  104. package/dist/core/oauth/openai.js.map +1 -1
  105. package/dist/core/oauth/pkce.d.ts +1 -4
  106. package/dist/core/oauth/pkce.d.ts.map +1 -1
  107. package/dist/core/oauth/pkce.js +2 -16
  108. package/dist/core/oauth/pkce.js.map +1 -1
  109. package/dist/core/oauth/types.d.ts +1 -13
  110. package/dist/core/oauth/types.d.ts.map +1 -1
  111. package/dist/core/process-manager.d.ts +10 -0
  112. package/dist/core/process-manager.d.ts.map +1 -1
  113. package/dist/core/process-manager.js +45 -1
  114. package/dist/core/process-manager.js.map +1 -1
  115. package/dist/core/prompt-commands.d.ts.map +1 -1
  116. package/dist/core/prompt-commands.js +20 -7
  117. package/dist/core/prompt-commands.js.map +1 -1
  118. package/dist/core/session-manager.d.ts +26 -0
  119. package/dist/core/session-manager.d.ts.map +1 -1
  120. package/dist/core/session-manager.js +99 -2
  121. package/dist/core/session-manager.js.map +1 -1
  122. package/dist/core/session-manager.test.d.ts +2 -0
  123. package/dist/core/session-manager.test.d.ts.map +1 -0
  124. package/dist/core/session-manager.test.js +113 -0
  125. package/dist/core/session-manager.test.js.map +1 -0
  126. package/dist/core/settings-manager.d.ts +2 -0
  127. package/dist/core/settings-manager.d.ts.map +1 -1
  128. package/dist/core/settings-manager.js +6 -0
  129. package/dist/core/settings-manager.js.map +1 -1
  130. package/dist/core/telegram.d.ts +1 -112
  131. package/dist/core/telegram.d.ts.map +1 -1
  132. package/dist/core/telegram.js +2 -251
  133. package/dist/core/telegram.js.map +1 -1
  134. package/dist/core/thinking-level.d.ts +1 -4
  135. package/dist/core/thinking-level.d.ts.map +1 -1
  136. package/dist/core/thinking-level.js +3 -58
  137. package/dist/core/thinking-level.js.map +1 -1
  138. package/dist/core/voice-transcriber.d.ts +1 -32
  139. package/dist/core/voice-transcriber.d.ts.map +1 -1
  140. package/dist/core/voice-transcriber.js +3 -112
  141. package/dist/core/voice-transcriber.js.map +1 -1
  142. package/dist/interactive.d.ts.map +1 -1
  143. package/dist/interactive.js +5 -2
  144. package/dist/interactive.js.map +1 -1
  145. package/dist/modes/agent-home-mode.d.ts.map +1 -1
  146. package/dist/modes/agent-home-mode.js +10 -38
  147. package/dist/modes/agent-home-mode.js.map +1 -1
  148. package/dist/modes/serve-mode.d.ts.map +1 -1
  149. package/dist/modes/serve-mode.js +11 -39
  150. package/dist/modes/serve-mode.js.map +1 -1
  151. package/dist/session.d.ts.map +1 -1
  152. package/dist/session.js +13 -6
  153. package/dist/session.js.map +1 -1
  154. package/dist/tools/bash.d.ts.map +1 -1
  155. package/dist/tools/bash.js +6 -3
  156. package/dist/tools/bash.js.map +1 -1
  157. package/dist/tools/edit.d.ts +3 -1
  158. package/dist/tools/edit.d.ts.map +1 -1
  159. package/dist/tools/edit.js +15 -3
  160. package/dist/tools/edit.js.map +1 -1
  161. package/dist/tools/edit.test.js +70 -0
  162. package/dist/tools/edit.test.js.map +1 -1
  163. package/dist/tools/goals.d.ts +1 -1
  164. package/dist/tools/index.d.ts +15 -0
  165. package/dist/tools/index.d.ts.map +1 -1
  166. package/dist/tools/index.js +16 -3
  167. package/dist/tools/index.js.map +1 -1
  168. package/dist/tools/task-send.d.ts +12 -0
  169. package/dist/tools/task-send.d.ts.map +1 -0
  170. package/dist/tools/task-send.js +34 -0
  171. package/dist/tools/task-send.js.map +1 -0
  172. package/dist/tools/task-send.test.d.ts +2 -0
  173. package/dist/tools/task-send.test.d.ts.map +1 -0
  174. package/dist/tools/task-send.test.js +95 -0
  175. package/dist/tools/task-send.test.js.map +1 -0
  176. package/dist/tools/write.d.ts +3 -1
  177. package/dist/tools/write.d.ts.map +1 -1
  178. package/dist/tools/write.js +13 -2
  179. package/dist/tools/write.js.map +1 -1
  180. package/dist/tools/write.test.js +33 -0
  181. package/dist/tools/write.test.js.map +1 -1
  182. package/dist/types.d.ts +2 -0
  183. package/dist/types.d.ts.map +1 -1
  184. package/dist/ui/App.d.ts +16 -0
  185. package/dist/ui/App.d.ts.map +1 -1
  186. package/dist/ui/App.js +126 -12
  187. package/dist/ui/App.js.map +1 -1
  188. package/dist/ui/anchor-pad-reclaim.test.d.ts +2 -0
  189. package/dist/ui/anchor-pad-reclaim.test.d.ts.map +1 -0
  190. package/dist/ui/anchor-pad-reclaim.test.js +146 -0
  191. package/dist/ui/anchor-pad-reclaim.test.js.map +1 -0
  192. package/dist/ui/app-items.d.ts +14 -0
  193. package/dist/ui/app-items.d.ts.map +1 -1
  194. package/dist/ui/app-items.js +13 -0
  195. package/dist/ui/app-items.js.map +1 -1
  196. package/dist/ui/app-items.test.js +17 -1
  197. package/dist/ui/app-items.test.js.map +1 -1
  198. package/dist/ui/atomic-flush-regression.test.d.ts +2 -0
  199. package/dist/ui/atomic-flush-regression.test.d.ts.map +1 -0
  200. package/dist/ui/atomic-flush-regression.test.js +218 -0
  201. package/dist/ui/atomic-flush-regression.test.js.map +1 -0
  202. package/dist/ui/components/AssistantMessage.test.js +32 -1
  203. package/dist/ui/components/AssistantMessage.test.js.map +1 -1
  204. package/dist/ui/components/Footer.d.ts.map +1 -1
  205. package/dist/ui/components/Footer.js +1 -0
  206. package/dist/ui/components/Footer.js.map +1 -1
  207. package/dist/ui/components/InputArea.test.js +11 -5
  208. package/dist/ui/components/InputArea.test.js.map +1 -1
  209. package/dist/ui/components/Markdown.d.ts.map +1 -1
  210. package/dist/ui/components/Markdown.js +9 -1
  211. package/dist/ui/components/Markdown.js.map +1 -1
  212. package/dist/ui/footer-jump-regression.test.js +56 -9
  213. package/dist/ui/footer-jump-regression.test.js.map +1 -1
  214. package/dist/ui/frame-clip-regression.test.d.ts +2 -0
  215. package/dist/ui/frame-clip-regression.test.d.ts.map +1 -0
  216. package/dist/ui/frame-clip-regression.test.js +215 -0
  217. package/dist/ui/frame-clip-regression.test.js.map +1 -0
  218. package/dist/ui/hooks/useTranscriptHistory.d.ts +9 -1
  219. package/dist/ui/hooks/useTranscriptHistory.d.ts.map +1 -1
  220. package/dist/ui/hooks/useTranscriptHistory.js +51 -19
  221. package/dist/ui/hooks/useTranscriptHistory.js.map +1 -1
  222. package/dist/ui/hooks/useTranscriptHistory.sessionstore.test.d.ts +2 -0
  223. package/dist/ui/hooks/useTranscriptHistory.sessionstore.test.d.ts.map +1 -0
  224. package/dist/ui/hooks/useTranscriptHistory.sessionstore.test.js +81 -0
  225. package/dist/ui/hooks/useTranscriptHistory.sessionstore.test.js.map +1 -0
  226. package/dist/ui/live-area-clamp.test.js +37 -0
  227. package/dist/ui/live-area-clamp.test.js.map +1 -1
  228. package/dist/ui/live-area-height.d.ts.map +1 -1
  229. package/dist/ui/live-area-height.js +13 -6
  230. package/dist/ui/live-area-height.js.map +1 -1
  231. package/dist/ui/live-area-height.test.js +9 -0
  232. package/dist/ui/live-area-height.test.js.map +1 -1
  233. package/dist/ui/live-item-flush.d.ts +28 -0
  234. package/dist/ui/live-item-flush.d.ts.map +1 -1
  235. package/dist/ui/live-item-flush.js +44 -0
  236. package/dist/ui/live-item-flush.js.map +1 -1
  237. package/dist/ui/live-item-flush.test.js +83 -1
  238. package/dist/ui/live-item-flush.test.js.map +1 -1
  239. package/dist/ui/login.d.ts.map +1 -1
  240. package/dist/ui/login.js +12 -30
  241. package/dist/ui/login.js.map +1 -1
  242. package/dist/ui/mcp.d.ts +1 -2
  243. package/dist/ui/mcp.d.ts.map +1 -1
  244. package/dist/ui/mcp.js +8 -43
  245. package/dist/ui/mcp.js.map +1 -1
  246. package/dist/ui/pixel.d.ts.map +1 -1
  247. package/dist/ui/pixel.js +10 -38
  248. package/dist/ui/pixel.js.map +1 -1
  249. package/dist/ui/queued-message.test.js +7 -1
  250. package/dist/ui/queued-message.test.js.map +1 -1
  251. package/dist/ui/render.d.ts.map +1 -1
  252. package/dist/ui/render.js +154 -5
  253. package/dist/ui/render.js.map +1 -1
  254. package/dist/ui/sessions.d.ts.map +1 -1
  255. package/dist/ui/sessions.js +10 -42
  256. package/dist/ui/sessions.js.map +1 -1
  257. package/dist/ui/startup-banner-order.test.d.ts +2 -0
  258. package/dist/ui/startup-banner-order.test.d.ts.map +1 -0
  259. package/dist/ui/startup-banner-order.test.js +105 -0
  260. package/dist/ui/startup-banner-order.test.js.map +1 -0
  261. package/dist/ui/streaming-flush-bounce.test.js +41 -20
  262. package/dist/ui/streaming-flush-bounce.test.js.map +1 -1
  263. package/dist/ui/submit-boundary-gap.test.d.ts +2 -0
  264. package/dist/ui/submit-boundary-gap.test.d.ts.map +1 -0
  265. package/dist/ui/submit-boundary-gap.test.js +180 -0
  266. package/dist/ui/submit-boundary-gap.test.js.map +1 -0
  267. package/dist/ui/terminal-history.d.ts.map +1 -1
  268. package/dist/ui/terminal-history.js +28 -9
  269. package/dist/ui/terminal-history.js.map +1 -1
  270. package/dist/ui/terminal-history.test.js +10 -3
  271. package/dist/ui/terminal-history.test.js.map +1 -1
  272. package/dist/ui/testing/screen-recorder.d.ts.map +1 -1
  273. package/dist/ui/testing/screen-recorder.js +11 -1
  274. package/dist/ui/testing/screen-recorder.js.map +1 -1
  275. package/dist/ui/tui-history-parity.test.js +4 -1
  276. package/dist/ui/tui-history-parity.test.js.map +1 -1
  277. package/dist/ui/tui-simulation.test.js +7 -1
  278. package/dist/ui/tui-simulation.test.js.map +1 -1
  279. package/dist/ui/utils/assistant-stream-split.d.ts +7 -0
  280. package/dist/ui/utils/assistant-stream-split.d.ts.map +1 -1
  281. package/dist/ui/utils/assistant-stream-split.js +22 -1
  282. package/dist/ui/utils/assistant-stream-split.js.map +1 -1
  283. package/dist/ui/utils/assistant-stream-split.test.js +14 -0
  284. package/dist/ui/utils/assistant-stream-split.test.js.map +1 -1
  285. package/dist/ui/utils/markdown-renderer.d.ts.map +1 -1
  286. package/dist/ui/utils/markdown-renderer.js +46 -3
  287. package/dist/ui/utils/markdown-renderer.js.map +1 -1
  288. package/dist/ui/utils/markdown-renderer.test.d.ts +2 -0
  289. package/dist/ui/utils/markdown-renderer.test.d.ts.map +1 -0
  290. package/dist/ui/utils/markdown-renderer.test.js +63 -0
  291. package/dist/ui/utils/markdown-renderer.test.js.map +1 -0
  292. package/dist/ui/utils/terminal-graphics.d.ts +28 -1
  293. package/dist/ui/utils/terminal-graphics.d.ts.map +1 -1
  294. package/dist/ui/utils/terminal-graphics.js +44 -3
  295. package/dist/ui/utils/terminal-graphics.js.map +1 -1
  296. package/dist/ui/utils/terminal-graphics.test.js +40 -1
  297. package/dist/ui/utils/terminal-graphics.test.js.map +1 -1
  298. package/dist/utils/session-title.js +1 -1
  299. package/dist/utils/session-title.js.map +1 -1
  300. package/package.json +7 -6
  301. package/dist/core/model-registry.test.d.ts +0 -2
  302. package/dist/core/model-registry.test.d.ts.map +0 -1
  303. package/dist/core/model-registry.test.js +0 -95
  304. package/dist/core/model-registry.test.js.map +0 -1
  305. package/dist/core/oauth/gemini.test.d.ts +0 -2
  306. package/dist/core/oauth/gemini.test.d.ts.map +0 -1
  307. package/dist/core/oauth/gemini.test.js +0 -154
  308. package/dist/core/oauth/gemini.test.js.map +0 -1
  309. package/dist/core/thinking-level.test.d.ts +0 -2
  310. package/dist/core/thinking-level.test.d.ts.map +0 -1
  311. package/dist/core/thinking-level.test.js +0 -51
  312. package/dist/core/thinking-level.test.js.map +0 -1
  313. package/dist/core/voice-transcriber.test.d.ts +0 -2
  314. package/dist/core/voice-transcriber.test.d.ts.map +0 -1
  315. package/dist/core/voice-transcriber.test.js +0 -88
  316. package/dist/core/voice-transcriber.test.js.map +0 -1
@@ -2,14 +2,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Regression for the per-chunk footer "bounce" during streaming.
3
3
  //
4
4
  // The assistant response is flushed to native scrollback paragraph-by-paragraph
5
- // while it streams (see assistant-stream-split). The scrollback print happens in
6
- // an effect AFTER paint, while `flushedChars` only advances in that same effect.
7
- // If the live frame is sliced solely by the already-committed `flushedChars`,
8
- // the just-flushed paragraph renders BOTH in scrollback and still live for one
9
- // frame a transient height bump that shoves the footer up, then back down on
10
- // every paragraph boundary. App fixes this by slicing the live text with the
11
- // PROSPECTIVE flush computed during render. This test models that flow and
12
- // asserts the footer offset from the bottom stays constant across flushes.
5
+ // while it streams (see assistant-stream-split). The flush effect calls
6
+ // queueFlush, which renders the paragraph to ANSI and enqueues the bytes
7
+ // through the patched Ink `insertBeforeFrame` (a passive buffer no terminal
8
+ // write), then advances `flushedChars` via the same React batch. The commit
9
+ // that shrinks the live text therefore carries the compensating scrollback
10
+ // bytes in ONE frame write: erase tall frame + paragraph bytes + shorter
11
+ // frame. No frame ever shows the paragraph in both scrollback and the live
12
+ // region, so the footer offset from the bottom stays constant. This test
13
+ // models that flow and asserts exactly that.
13
14
  import React, { useEffect, useRef, useState } from "react";
14
15
  import { render, Text, useStdout } from "ink";
15
16
  import { describe, expect, it } from "vitest";
@@ -41,18 +42,24 @@ const getId = () => `chunk-${idCounter++}`;
41
42
  function Controls() {
42
43
  return (_jsxs(ChatControls, { controlsRef: () => { }, children: [_jsx(Text, { children: "SIM_STATUS" }), _jsx(Text, { children: "SIM_INPUT" }), _jsx(Text, { children: "SIM_FOOTER" }), _jsx(Text, { children: FOOTER_BOTTOM })] }));
43
44
  }
44
- // Mirrors App.tsx: prospective flush is computed during render so the live
45
- // frame drops the to-be-flushed prefix immediately; the scrollback print is
46
- // deferred to the effect.
47
- function Driver({ rawText }) {
45
+ // Mirrors App.tsx: the live text is sliced by the COMMITTED `flushedChars`
46
+ // only. The flush effect enqueues the paragraph bytes (passive) and advances
47
+ // `flushedChars`; the resulting commit's single frame write both shrinks the
48
+ // live frame and carries the scrollback bytes.
49
+ function Driver({ rawText, enqueueStdout, }) {
48
50
  const { write: writeStdout } = useStdout();
49
51
  const [history, setHistory] = useState([]);
50
52
  const [liveItems, setLiveItems] = useState([]);
51
53
  const flushRef = useRef({ flushedChars: 0, text: "" });
54
+ // Stable printer across renders — mirrors renderApp, where one printer
55
+ // instance (and its printed-id dedup set) lives for the whole app lifetime.
56
+ const printerRef = useRef(null);
57
+ printerRef.current ??= createTerminalHistoryPrinter();
52
58
  const { queueFlush } = useTranscriptHistory({
53
- terminalHistoryPrinter: createTerminalHistoryPrinter(),
59
+ terminalHistoryPrinter: printerRef.current,
54
60
  terminalHistoryContext: terminalContext,
55
61
  writeStdout,
62
+ enqueueStdout,
56
63
  sessionPathRef: { current: undefined },
57
64
  sessionManagerRef: { current: null },
58
65
  history,
@@ -60,9 +67,6 @@ function Driver({ rawText }) {
60
67
  setLiveItems,
61
68
  });
62
69
  const alreadyFlushed = flushRef.current.flushedChars;
63
- const pendingFlushChars = rawText
64
- ? splitAssistantStreamingText(rawText.slice(alreadyFlushed)).flushedText.length
65
- : 0;
66
70
  useEffect(() => {
67
71
  if (!rawText) {
68
72
  flushRef.current = { flushedChars: 0, text: "" };
@@ -88,7 +92,7 @@ function Driver({ rawText }) {
88
92
  }
89
93
  flushRef.current = { ...flushRef.current, text: rawText };
90
94
  }, [rawText, queueFlush]);
91
- const visibleStreamingText = stripDoneMarkers(rawText.slice(alreadyFlushed + pendingFlushChars));
95
+ const visibleStreamingText = stripDoneMarkers(rawText.slice(alreadyFlushed));
92
96
  const renderItem = (item, index, items) => renderTranscriptItem({
93
97
  item,
94
98
  index,
@@ -102,7 +106,10 @@ function Driver({ rawText }) {
102
106
  renderMarkdown: true,
103
107
  measuredLiveAreaRows: ROWS,
104
108
  });
105
- return (_jsx(ThemeContext.Provider, { value: theme, children: _jsx(TerminalSizeProvider, { children: _jsxs(ChatLayout, { columns: COLUMNS, children: [_jsx(ChatLivePane, { liveItems: liveItems, renderItem: renderItem, isRunning: true, visibleStreamingText: visibleStreamingText, streamingThinking: "", thinkingMs: 0, reserveStreamingSpacing: false, renderMarkdown: true, measuredLiveAreaRows: ROWS, assistantMarginTop: 0, streamingContinuation: alreadyFlushed + pendingFlushChars > 0 }), _jsx(Controls, {})] }) }) }));
109
+ return (_jsx(ThemeContext.Provider, { value: theme, children: _jsx(TerminalSizeProvider, { children: _jsxs(ChatLayout, { columns: COLUMNS, children: [_jsx(ChatLivePane, { liveItems: liveItems, renderItem: renderItem, isRunning: true, visibleStreamingText: visibleStreamingText, streamingThinking: "", thinkingMs: 0, reserveStreamingSpacing: false, renderMarkdown: true, measuredLiveAreaRows: ROWS,
110
+ // Mirrors App: a live continuation of an already-flushed stream
111
+ // re-inserts the blank separator line above the live remainder.
112
+ assistantMarginTop: alreadyFlushed > 0 ? 1 : 0, streamingContinuation: alreadyFlushed > 0 }), _jsx(Controls, {})] }) }) }));
106
113
  }
107
114
  async function tick() {
108
115
  await new Promise((resolve) => setTimeout(resolve, 45));
@@ -114,6 +121,19 @@ describe("streaming flush footer bounce", () => {
114
121
  it("keeps the footer at a constant offset across paragraph flush boundaries", async () => {
115
122
  const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
116
123
  const stdout = makeRecordingStdout(recorder);
124
+ // Mirrors renderApp's enqueueHistoryWrite: reads the (patched) instance at
125
+ // call time, falling back to a raw write when the API is unavailable.
126
+ const instanceRef = {
127
+ current: null,
128
+ };
129
+ const enqueueStdout = (data) => {
130
+ if (instanceRef.current?.insertBeforeFrame) {
131
+ instanceRef.current.insertBeforeFrame(data);
132
+ }
133
+ else {
134
+ stdout.write(data);
135
+ }
136
+ };
117
137
  const footerOffsets = [];
118
138
  const capture = () => {
119
139
  const lines = recorder.viewportLines().map((line) => stripAnsi(line));
@@ -130,17 +150,18 @@ describe("streaming flush footer bounce", () => {
130
150
  `${paragraph(1)}\n\n${paragraph(2)}\n\n`,
131
151
  `${paragraph(1)}\n\n${paragraph(2)}\n\n${paragraph(3)}`,
132
152
  ];
133
- const mounted = render(_jsx(Driver, { rawText: steps[0] }), {
153
+ const mounted = render(_jsx(Driver, { rawText: steps[0], enqueueStdout: enqueueStdout }), {
134
154
  stdout,
135
155
  columns: COLUMNS,
136
156
  rows: ROWS,
137
157
  patchConsole: false,
138
158
  maxFps: 1000,
139
159
  });
160
+ instanceRef.current = mounted;
140
161
  await tick();
141
162
  capture();
142
163
  for (let i = 1; i < steps.length; i++) {
143
- mounted.rerender(_jsx(Driver, { rawText: steps[i] }));
164
+ mounted.rerender(_jsx(Driver, { rawText: steps[i], enqueueStdout: enqueueStdout }));
144
165
  await tick();
145
166
  await tick();
146
167
  capture();
@@ -1 +1 @@
1
- {"version":3,"file":"streaming-flush-bounce.test.js","sourceRoot":"","sources":["../../src/ui/streaming-flush-bounce.test.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"streaming-flush-bounce.test.js","sourceRoot":"","sources":["../../src/ui/streaming-flush-bounce.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=submit-boundary-gap.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submit-boundary-gap.test.d.ts","sourceRoot":"","sources":["../../src/ui/submit-boundary-gap.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,180 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Self-test for the "white space before the LLM responds" gap that appears
3
+ // when submitting a slash command (e.g. /commit).
4
+ //
5
+ // The submit boundary stacks several frame SHRINKS into the moment the agent
6
+ // is (re)starting, while the patched ink bottom-anchor is ACTIVE:
7
+ // 1. the prior turn's live items flush to scrollback + the live frame clears
8
+ // (setLiveItems([])),
9
+ // 2. the open slash menu closes (the controls block gets shorter).
10
+ // Each shrink-while-active becomes blank "pad debt" above the frame so the
11
+ // footer can't jump up. The new turn then enters a quiet thinking phase with
12
+ // NO frame growth, so nothing consumes the pads — they sit on screen as a blank
13
+ // gap before any LLM output, exactly what the user reported.
14
+ //
15
+ // This drives the REAL patched ink through that byte sequence and asserts:
16
+ // A) the gap genuinely forms at the submit boundary (the bug), and
17
+ // B) the mid-run off→on reclaim pulse (App's fix) collapses it while keeping
18
+ // the footer bottom-pinned.
19
+ import React from "react";
20
+ import { Box, render, Text } from "ink";
21
+ import { describe, expect, it } from "vitest";
22
+ import stripAnsi from "strip-ansi";
23
+ import { ScreenRecorder, makeRecordingStdout } from "./testing/screen-recorder.js";
24
+ const COLUMNS = 80;
25
+ const ROWS = 24;
26
+ const FOOTER = "SIM_FOOTER_BOTTOM";
27
+ const FRAME_TOP = "SIM_FRAME_TOP";
28
+ // Controls block: the slash menu adds rows ABOVE the input while open, so
29
+ // closing it is a real controls-height shrink (same shape as the menu close
30
+ // that fires on submit).
31
+ function Controls({ liveRows, menuOpen }) {
32
+ return (_jsxs(Box, { flexDirection: "column", width: COLUMNS, children: [_jsx(Text, { children: FRAME_TOP }), Array.from({ length: liveRows }, (_, i) => (_jsxs(Text, { children: ["SIM_LIVE_", i + 1] }, i))), menuOpen
33
+ ? Array.from({ length: 6 }, (_, i) => _jsxs(Text, { children: ["SIM_MENU_ITEM_", i + 1] }, `m${i}`))
34
+ : null, _jsx(Text, { children: "SIM_INPUT" }), _jsx(Text, { children: FOOTER })] }));
35
+ }
36
+ async function tick(ms = 60) {
37
+ await new Promise((resolve) => setTimeout(resolve, ms));
38
+ }
39
+ function blanksAboveFrame(recorder) {
40
+ const lines = recorder.viewportLines().map((line) => stripAnsi(line));
41
+ const frameTopIdx = lines.findIndex((line) => line.includes(FRAME_TOP));
42
+ if (frameTopIdx < 0)
43
+ return -1;
44
+ return lines.slice(0, frameTopIdx).filter((line) => line.trim() === "").length;
45
+ }
46
+ describe("submit-boundary gap", () => {
47
+ it("forms the gap at submit then the reclaim pulse collapses it (footer stays pinned)", async () => {
48
+ const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
49
+ const stdout = makeRecordingStdout(recorder);
50
+ const transcriptRows = Array.from({ length: ROWS }, (_, i) => `SIM_HISTORY_${i + 1}`);
51
+ const mounted = render(_jsx(Controls, { liveRows: 2, menuOpen: false }), {
52
+ stdout,
53
+ patchConsole: false,
54
+ maxFps: 1000,
55
+ anchorFrameToBottom: true,
56
+ clipFrameToTerminalHeight: true,
57
+ });
58
+ // Skip cleanly on unpatched ink (fork-only APIs).
59
+ if (!mounted.setFrameAnchorActive || !mounted.insertBeforeFrame) {
60
+ mounted.unmount();
61
+ return;
62
+ }
63
+ mounted.setFrameShrinkBackfill?.((needRows) => {
64
+ const lines = transcriptRows.slice(-needRows);
65
+ while (lines.length < needRows)
66
+ lines.unshift("");
67
+ return `${lines.join("\n")}\n`;
68
+ });
69
+ await tick();
70
+ // Prior turn finished streaming a tall response; the slash menu is open
71
+ // because the user typed "/commit". Anchor is ACTIVE (agent considered
72
+ // running through the submit). Flush the scrollback history first.
73
+ mounted.setFrameAnchorActive(true);
74
+ mounted.insertBeforeFrame(`${transcriptRows.join("\n")}\n`);
75
+ mounted.rerender(_jsx(Controls, { liveRows: 10, menuOpen: true }));
76
+ await tick();
77
+ // SUBMIT: prior live rows flush + clear (live shrinks to 1) AND the slash
78
+ // menu closes — two stacked shrinks with NO compensating scrollback insert,
79
+ // while the anchor is active. Pad debt forms.
80
+ mounted.rerender(_jsx(Controls, { liveRows: 1, menuOpen: false }));
81
+ await tick();
82
+ // Quiet thinking phase: nothing grows the frame. The gap is now on screen
83
+ // BEFORE any LLM output — the reported bug.
84
+ const gapAtSubmit = blanksAboveFrame(recorder);
85
+ expect(gapAtSubmit, "gap genuinely forms at the submit boundary").toBeGreaterThan(0);
86
+ // App's mid-run reclaim pulse (fires after the frame is stable): off→on.
87
+ mounted.setFrameAnchorActive(false);
88
+ mounted.setFrameAnchorActive(true);
89
+ await tick(150);
90
+ const lines = recorder.viewportLines().map((line) => stripAnsi(line));
91
+ const frameTopIdx = lines.findIndex((line) => line.includes(FRAME_TOP));
92
+ const footerIdx = lines.findIndex((line) => line.includes(FOOTER));
93
+ expect(frameTopIdx, "live frame visible").toBeGreaterThanOrEqual(0);
94
+ expect(footerIdx, "footer visible").toBeGreaterThan(frameTopIdx);
95
+ // The gap is gone.
96
+ expect(blanksAboveFrame(recorder), "reclaim pulse collapses the submit-boundary gap").toBe(0);
97
+ // Footer still bottom-pinned (nothing below it).
98
+ const lastNonBlank = lines.reduce((acc, line, i) => (line.trim().length > 0 ? i : acc), -1);
99
+ expect(lastNonBlank, "nothing below the footer after reclaim").toBe(footerIdx);
100
+ mounted.unmount();
101
+ });
102
+ it("reclaims a finishing tool-batch collapse mid-run (no menu involved)", async () => {
103
+ const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
104
+ const stdout = makeRecordingStdout(recorder);
105
+ const transcriptRows = Array.from({ length: ROWS }, (_, i) => `SIM_HISTORY_${i + 1}`);
106
+ const mounted = render(_jsx(Controls, { liveRows: 2, menuOpen: false }), {
107
+ stdout,
108
+ patchConsole: false,
109
+ maxFps: 1000,
110
+ anchorFrameToBottom: true,
111
+ clipFrameToTerminalHeight: true,
112
+ });
113
+ if (!mounted.setFrameAnchorActive || !mounted.insertBeforeFrame) {
114
+ mounted.unmount();
115
+ return;
116
+ }
117
+ mounted.setFrameShrinkBackfill?.((needRows) => {
118
+ const lines = transcriptRows.slice(-needRows);
119
+ while (lines.length < needRows)
120
+ lines.unshift("");
121
+ return `${lines.join("\n")}\n`;
122
+ });
123
+ await tick();
124
+ // Mid-run: a batch of tool panels expands the live frame, then collapses
125
+ // when the batch finishes — a shrink with no compensating insert. Debt forms.
126
+ mounted.setFrameAnchorActive(true);
127
+ mounted.insertBeforeFrame(`${transcriptRows.join("\n")}\n`);
128
+ mounted.rerender(_jsx(Controls, { liveRows: 11, menuOpen: false }));
129
+ await tick();
130
+ mounted.rerender(_jsx(Controls, { liveRows: 2, menuOpen: false }));
131
+ await tick();
132
+ expect(blanksAboveFrame(recorder), "tool-batch collapse forms a gap").toBeGreaterThan(0);
133
+ // Reclaim pulse collapses it.
134
+ mounted.setFrameAnchorActive(false);
135
+ mounted.setFrameAnchorActive(true);
136
+ await tick(150);
137
+ expect(blanksAboveFrame(recorder), "tool-batch gap reclaimed").toBe(0);
138
+ mounted.unmount();
139
+ });
140
+ it("is a harmless no-op when there is no pad debt (steady streaming)", async () => {
141
+ const recorder = new ScreenRecorder({ columns: COLUMNS, rows: ROWS });
142
+ const stdout = makeRecordingStdout(recorder);
143
+ const transcriptRows = Array.from({ length: ROWS }, (_, i) => `SIM_HISTORY_${i + 1}`);
144
+ const mounted = render(_jsx(Controls, { liveRows: 2, menuOpen: false }), {
145
+ stdout,
146
+ patchConsole: false,
147
+ maxFps: 1000,
148
+ anchorFrameToBottom: true,
149
+ clipFrameToTerminalHeight: true,
150
+ });
151
+ if (!mounted.setFrameAnchorActive || !mounted.insertBeforeFrame) {
152
+ mounted.unmount();
153
+ return;
154
+ }
155
+ mounted.setFrameShrinkBackfill?.((needRows) => {
156
+ const lines = transcriptRows.slice(-needRows);
157
+ while (lines.length < needRows)
158
+ lines.unshift("");
159
+ return `${lines.join("\n")}\n`;
160
+ });
161
+ await tick();
162
+ // Anchor active, frame only GROWS (no shrink) — no debt is ever created.
163
+ mounted.setFrameAnchorActive(true);
164
+ mounted.rerender(_jsx(Controls, { liveRows: 4, menuOpen: false }));
165
+ await tick();
166
+ expect(blanksAboveFrame(recorder), "no gap during pure growth").toBe(0);
167
+ // A reclaim pulse here must change nothing: footer pinned, no gap, frame
168
+ // unchanged — proving the pulse is a safe no-op when debt is zero.
169
+ mounted.setFrameAnchorActive(false);
170
+ mounted.setFrameAnchorActive(true);
171
+ await tick(150);
172
+ const lines = recorder.viewportLines().map((line) => stripAnsi(line));
173
+ const footerIdx = lines.findIndex((line) => line.includes(FOOTER));
174
+ const lastNonBlank = lines.reduce((acc, line, i) => (line.trim().length > 0 ? i : acc), -1);
175
+ expect(blanksAboveFrame(recorder), "still no gap after no-op pulse").toBe(0);
176
+ expect(lastNonBlank, "footer still pinned after no-op pulse").toBe(footerIdx);
177
+ mounted.unmount();
178
+ });
179
+ });
180
+ //# sourceMappingURL=submit-boundary-gap.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submit-boundary-gap.test.js","sourceRoot":"","sources":["../../src/ui/submit-boundary-gap.test.tsx"],"names":[],"mappings":";AAAA,2EAA2E;AAC3E,kDAAkD;AAClD,EAAE;AACF,6EAA6E;AAC7E,kEAAkE;AAClE,+EAA+E;AAC/E,2BAA2B;AAC3B,qEAAqE;AACrE,2EAA2E;AAC3E,6EAA6E;AAC7E,gFAAgF;AAChF,6DAA6D;AAC7D,EAAE;AACF,2EAA2E;AAC3E,qEAAqE;AACrE,+EAA+E;AAC/E,iCAAiC;AACjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnF,MAAM,OAAO,GAAG,EAAE,CAAC;AACnB,MAAM,IAAI,GAAG,EAAE,CAAC;AAEhB,MAAM,MAAM,GAAG,mBAAmB,CAAC;AACnC,MAAM,SAAS,GAAG,eAAe,CAAC;AAElC,0EAA0E;AAC1E,4EAA4E;AAC5E,yBAAyB;AACzB,SAAS,QAAQ,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAA2C;IAC/E,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,KAAK,EAAE,OAAO,aACxC,KAAC,IAAI,cAAE,SAAS,GAAQ,EACvB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAC1C,MAAC,IAAI,4BAAmB,CAAC,GAAG,CAAC,KAAlB,CAAC,CAAyB,CACtC,CAAC,EACD,QAAQ;gBACP,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAC,IAAI,iCAA8B,CAAC,GAAG,CAAC,KAA7B,IAAI,CAAC,EAAE,CAA8B,CAAC;gBACvF,CAAC,CAAC,IAAI,EACR,KAAC,IAAI,4BAAiB,EACtB,KAAC,IAAI,cAAE,MAAM,GAAQ,IACjB,CACP,CAAC;AACJ,CAAC;AAUD,KAAK,UAAU,IAAI,CAAC,EAAE,GAAG,EAAE;IACzB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAwB;IAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,MAAM,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACxE,IAAI,WAAW,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;AACjF,CAAC;AAED,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;QACjG,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEtF,MAAM,OAAO,GAAG,MAAM,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAI,EAAE;YACjE,MAAM;YACN,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,IAAI;YACZ,mBAAmB,EAAE,IAAI;YACzB,yBAAyB,EAAE,IAAI;SACA,CAA+B,CAAC;QAEjE,kDAAkD;QAClD,IAAI,CAAC,OAAO,CAAC,oBAAoB,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAChE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,QAAgB,EAAE,EAAE;YACpD,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,KAAK,CAAC,MAAM,GAAG,QAAQ;gBAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,EAAE,CAAC;QAEb,wEAAwE;QACxE,uEAAuE;QACvE,mEAAmE;QACnE,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,CAAC,QAAQ,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAI,CAAC,CAAC;QAC7D,MAAM,IAAI,EAAE,CAAC;QAEb,0EAA0E;QAC1E,4EAA4E;QAC5E,8CAA8C;QAC9C,OAAO,CAAC,QAAQ,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAI,CAAC,CAAC;QAC7D,MAAM,IAAI,EAAE,CAAC;QAEb,0EAA0E;QAC1E,4CAA4C;QAC5C,MAAM,WAAW,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,CAAC,WAAW,EAAE,4CAA4C,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAErF,yEAAyE;QACzE,OAAO,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACpC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;QAEhB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,MAAM,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;QACxE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACnE,MAAM,CAAC,WAAW,EAAE,oBAAoB,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;QAEjE,mBAAmB;QACnB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,iDAAiD,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAE9F,iDAAiD;QACjD,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5F,MAAM,CAAC,YAAY,EAAE,wCAAwC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE/E,OAAO,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACnF,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEtF,MAAM,OAAO,GAAG,MAAM,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAI,EAAE;YACjE,MAAM;YACN,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,IAAI;YACZ,mBAAmB,EAAE,IAAI;YACzB,yBAAyB,EAAE,IAAI;SACA,CAA+B,CAAC;QAEjE,IAAI,CAAC,OAAO,CAAC,oBAAoB,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAChE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,QAAgB,EAAE,EAAE;YACpD,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,KAAK,CAAC,MAAM,GAAG,QAAQ;gBAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,EAAE,CAAC;QAEb,yEAAyE;QACzE,8EAA8E;QAC9E,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,CAAC,iBAAiB,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,OAAO,CAAC,QAAQ,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,KAAK,GAAI,CAAC,CAAC;QAC9D,MAAM,IAAI,EAAE,CAAC;QACb,OAAO,CAAC,QAAQ,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAI,CAAC,CAAC;QAC7D,MAAM,IAAI,EAAE,CAAC;QACb,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,iCAAiC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAEzF,8BAA8B;QAC9B,OAAO,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACpC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,0BAA0B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAEvE,OAAO,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAChF,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC7C,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEtF,MAAM,OAAO,GAAG,MAAM,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAI,EAAE;YACjE,MAAM;YACN,YAAY,EAAE,KAAK;YACnB,MAAM,EAAE,IAAI;YACZ,mBAAmB,EAAE,IAAI;YACzB,yBAAyB,EAAE,IAAI;SACA,CAA+B,CAAC;QAEjE,IAAI,CAAC,OAAO,CAAC,oBAAoB,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAChE,OAAO,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC,QAAgB,EAAE,EAAE;YACpD,MAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,KAAK,CAAC,MAAM,GAAG,QAAQ;gBAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,EAAE,CAAC;QAEb,yEAAyE;QACzE,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACnC,OAAO,CAAC,QAAQ,CAAC,KAAC,QAAQ,IAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAI,CAAC,CAAC;QAC7D,MAAM,IAAI,EAAE,CAAC;QACb,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,2BAA2B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAExE,yEAAyE;QACzE,mEAAmE;QACnE,OAAO,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACpC,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACnC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;QAEhB,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACnE,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5F,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,gCAAgC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC7E,MAAM,CAAC,YAAY,EAAE,uCAAuC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAE9E,OAAO,CAAC,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-history.d.ts","sourceRoot":"","sources":["../../src/ui/terminal-history.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAK9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AA2F9C,MAAM,WAAW,sBAAsB;IACrC,KAAK,CACH,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,OAAO,EAAE,sBAAsB,EAC/B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAC5D,IAAI,CAAC;IACR,KAAK,IAAI,IAAI,CAAC;IACd,YAAY,IAAI,IAAI,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AASD,wBAAgB,4BAA4B,CAAC,EAC3C,MAAuB,GACxB,GAAE,6BAAkC,GAAG,sBAAsB,CAiH7D;AAED,wBAAgB,uCAAuC,CACrD,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,sBAAsB,GAC9B,MAAM,CA+IR"}
1
+ {"version":3,"file":"terminal-history.d.ts","sourceRoot":"","sources":["../../src/ui/terminal-history.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAK9C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AA8F9C,MAAM,WAAW,sBAAsB;IACrC,KAAK,CACH,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,OAAO,EAAE,sBAAsB,EAC/B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAC5D,IAAI,CAAC;IACR,KAAK,IAAI,IAAI,CAAC;IACd,YAAY,IAAI,IAAI,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC;CAC7B;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;CACb;AASD,wBAAgB,4BAA4B,CAAC,EAC3C,MAAuB,GACxB,GAAE,6BAAkC,GAAG,sBAAsB,CAsH7D;AAED,wBAAgB,uCAAuC,CACrD,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,sBAAsB,GAC9B,MAAM,CA+IR"}
@@ -10,7 +10,7 @@ import { buildToolGroupSummary } from "./tool-group-summary.js";
10
10
  import path from "node:path";
11
11
  import { pathToFileURL } from "node:url";
12
12
  import { renderMarkdownToAnsiLines } from "./utils/markdown-renderer.js";
13
- import { detectGraphicsProtocol, encodeInlineImage } from "./utils/terminal-graphics.js";
13
+ import { detectGraphicsProtocol, encodeInlineImageBlock } from "./utils/terminal-graphics.js";
14
14
  import { createHyperlink } from "./utils/hyperlink.js";
15
15
  import { supportsHyperlinks } from "./utils/supports-hyperlinks.js";
16
16
  import { shouldSeparateTranscriptItems } from "./transcript/spacing.js";
@@ -51,6 +51,9 @@ const GRADIENT = [
51
51
  const GAP = " ";
52
52
  const LOGO_WIDTH = 9;
53
53
  const SIDE_BY_SIDE_MIN = LOGO_WIDTH + GAP.length + 62;
54
+ // Row index in the logo block where each info line is placed. The EZ art is
55
+ // 3 rows tall, so info lines sit one-per-row beside it from the top.
56
+ const INFO_ANCHOR_ROW = 0;
54
57
  const COMPACT_TOOLS = new Set(["read", "grep", "find", "ls", "source_path"]);
55
58
  const STATE_TOOLS = new Set(["tasks"]);
56
59
  const SERVER_STYLE_TOOLS = new Set(["web_search"]);
@@ -142,7 +145,10 @@ export function createTerminalHistoryPrinter({ stream = process.stdout, } = {})
142
145
  const canLink = supportsHyperlinks();
143
146
  for (const preview of previews) {
144
147
  if (protocol !== "none") {
145
- writeOutput(`\n${imageLeftPad}${encodeInlineImage(preview.base64, protocol)}\n`);
148
+ // Fixed-height block whose newline count equals its visual rows —
149
+ // a raw escape (many rows, zero newlines) desyncs Ink's live-frame
150
+ // erase math and strands orphaned rows around the image.
151
+ writeOutput(`\n${encodeInlineImageBlock(preview.base64, protocol, { leftPad: imageLeftPad })}\n`);
146
152
  }
147
153
  // Clickable "open" affordance — Cmd/Ctrl-click opens the file in the
148
154
  // OS default viewer. The pixels themselves aren't clickable, so the
@@ -311,13 +317,19 @@ function renderBanner(context) {
311
317
  "",
312
318
  ]);
313
319
  }
314
- return block([
315
- "",
316
- `${logo[0]}${GAP}${color(context.theme.primary, "EZ Coder", true)}${dim(context, ` v${context.version} · By `)}${color(context.theme.text, "Nolan Grout", true)}`,
317
- `${logo[1]}${GAP}${color(context.theme.secondary, modelName)} ${dim(context, truncatePlain(displayPath, Math.max(10, context.columns - LOGO_WIDTH - GAP.length - stringWidth(modelName) - 2)))}`,
318
- `${logo[2]}${GAP}${shortcuts}`,
319
- "",
320
- ]);
320
+ // Info lines rendered beside the (taller) logo. They're anchored starting at
321
+ // INFO_ANCHOR_ROW so the text column sits vertically centered next to the art.
322
+ const infoLines = [
323
+ `${color(context.theme.primary, "EZ Coder", true)}${dim(context, ` v${context.version} · By `)}${color(context.theme.text, "Nolan Grout", true)}`,
324
+ `${color(context.theme.secondary, modelName)} ${dim(context, truncatePlain(displayPath, Math.max(10, context.columns - LOGO_WIDTH - GAP.length - stringWidth(modelName) - 2)))}`,
325
+ shortcuts,
326
+ ];
327
+ const rows = logo.map((logoLine, i) => {
328
+ const infoIndex = i - INFO_ANCHOR_ROW;
329
+ const info = infoIndex >= 0 && infoIndex < infoLines.length ? infoLines[infoIndex] : undefined;
330
+ return info === undefined ? logoLine : `${logoLine}${GAP}${info}`;
331
+ });
332
+ return block(["", ...rows, ""]);
321
333
  }
322
334
  function renderGoalProgress(item, context) {
323
335
  const isError = item.status === "failed" || item.status === "fail" || item.status === "blocked";
@@ -860,6 +872,13 @@ function getServerToolHeaderParts(name, input) {
860
872
  function getBashExitCode(result) {
861
873
  return result.match(/^Exit code: (.+)/)?.[1]?.trim() ?? "0";
862
874
  }
875
+ /**
876
+ * At-a-glance header chip mirroring the live ToolExecution chips:
877
+ * - bash → exit-code chip (`✓ 0` / `✗ N`)
878
+ * - edit → diff-stat chip (`+a −r`)
879
+ * Returns "" when no chip applies. Spacing (two leading spaces per segment)
880
+ * matches the live renderer so live/history parity holds.
881
+ */
863
882
  function renderHeaderChip(name, result, isError, context) {
864
883
  if (name === "bash" && !isError) {
865
884
  const exitMatch = result.split("\n")[0]?.match(/^Exit code: (.+)/);