@deepseek-harness-tui/dsh-tui 0.9.3 → 0.10.0-beta.2

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 (267) hide show
  1. package/README.md +24 -6
  2. package/cordis.patch.yml +117 -39
  3. package/cordis.yml +13 -10
  4. package/lib/types/api.d.ts +7 -0
  5. package/lib/types/api.d.ts.map +1 -0
  6. package/lib/types/api.js +1 -0
  7. package/lib/types/cc/hyperlink.d.ts.map +1 -1
  8. package/lib/types/cc/hyperlink.js +42 -4
  9. package/lib/types/cc/markdown.d.ts.map +1 -1
  10. package/lib/types/cc/markdown.js +15 -5
  11. package/lib/types/commands.d.ts +6 -0
  12. package/lib/types/commands.d.ts.map +1 -1
  13. package/lib/types/commands.js +12 -8
  14. package/lib/types/components/MessageList.d.ts +8 -4
  15. package/lib/types/components/MessageList.d.ts.map +1 -1
  16. package/lib/types/components/MessageList.js +94 -32
  17. package/lib/types/components/PermissionsPicker.d.ts +6 -7
  18. package/lib/types/components/PermissionsPicker.d.ts.map +1 -1
  19. package/lib/types/components/PermissionsPicker.js +20 -14
  20. package/lib/types/components/PromptEditor.d.ts +46 -0
  21. package/lib/types/components/PromptEditor.d.ts.map +1 -0
  22. package/lib/types/components/PromptEditor.js +60 -0
  23. package/lib/types/components/PromptInput.d.ts +12 -0
  24. package/lib/types/components/PromptInput.d.ts.map +1 -1
  25. package/lib/types/components/PromptInput.js +1240 -205
  26. package/lib/types/components/RewindPicker.js +1 -1
  27. package/lib/types/components/SplitDiffView.d.ts +11 -1
  28. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  29. package/lib/types/components/SplitDiffView.js +9 -2
  30. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  31. package/lib/types/components/StreamingMarkdown.js +140 -3
  32. package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
  33. package/lib/types/components/SubagentDashboard.js +2 -2
  34. package/lib/types/components/SubagentDetailScene.js +1 -1
  35. package/lib/types/components/ThemePicker.d.ts +13 -15
  36. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  37. package/lib/types/components/ThemePicker.js +24 -26
  38. package/lib/types/components/Tooltip.d.ts +67 -0
  39. package/lib/types/components/Tooltip.d.ts.map +1 -0
  40. package/lib/types/components/Tooltip.js +220 -0
  41. package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
  42. package/lib/types/components/approvals/ApprovalPanel.js +1 -1
  43. package/lib/types/components/design-system/Divider.d.ts +14 -1
  44. package/lib/types/components/design-system/Divider.d.ts.map +1 -1
  45. package/lib/types/components/design-system/Divider.js +86 -8
  46. package/lib/types/components/design-system/ThemeProvider.d.ts +5 -2
  47. package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
  48. package/lib/types/components/design-system/ThemeProvider.js +52 -16
  49. package/lib/types/components/design-system/ThemedBox.d.ts +23 -2
  50. package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -1
  51. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +11 -6
  52. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  53. package/lib/types/components/messages/AssistantThinkingMessage.js +10 -6
  54. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +13 -1
  55. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/AssistantToolUseMessage.js +37 -6
  57. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  58. package/lib/types/components/messages/UserPromptMessage.js +7 -1
  59. package/lib/types/components/questions/AskUserQuestionPanel.js +1 -1
  60. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  61. package/lib/types/components/questions/PlanReviewPanel.js +29 -3
  62. package/lib/types/components/sessions/SessionListRow.d.ts +8 -1
  63. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  64. package/lib/types/components/sessions/SessionListRow.js +9 -2
  65. package/lib/types/components/sessions/SessionPreview.d.ts.map +1 -1
  66. package/lib/types/components/sessions/SessionPreview.js +5 -3
  67. package/lib/types/components/sessions/WorkspaceListRow.d.ts +17 -0
  68. package/lib/types/components/sessions/WorkspaceListRow.d.ts.map +1 -0
  69. package/lib/types/components/sessions/WorkspaceListRow.js +27 -0
  70. package/lib/types/components/smoothReveal.d.ts +97 -0
  71. package/lib/types/components/smoothReveal.d.ts.map +1 -0
  72. package/lib/types/components/smoothReveal.js +279 -0
  73. package/lib/types/customTheme.d.ts +4 -0
  74. package/lib/types/customTheme.d.ts.map +1 -1
  75. package/lib/types/customTheme.js +9 -5
  76. package/lib/types/dsh-adapter/approvals.d.ts +114 -2
  77. package/lib/types/dsh-adapter/approvals.d.ts.map +1 -1
  78. package/lib/types/dsh-adapter/approvals.js +241 -3
  79. package/lib/types/dsh-adapter/channel.d.ts +59 -2
  80. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  81. package/lib/types/dsh-adapter/channel.js +499 -80
  82. package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -1
  83. package/lib/types/dsh-adapter/childStderr.js +9 -0
  84. package/lib/types/dsh-adapter/contract.d.ts +17 -13
  85. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  86. package/lib/types/dsh-adapter/contract.js +36 -27
  87. package/lib/types/dsh-adapter/credentialRefGuard.d.ts +40 -0
  88. package/lib/types/dsh-adapter/credentialRefGuard.d.ts.map +1 -0
  89. package/lib/types/dsh-adapter/credentialRefGuard.js +43 -0
  90. package/lib/types/dsh-adapter/effect-ledger.d.ts +1 -1
  91. package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -1
  92. package/lib/types/dsh-adapter/effect-ledger.js +1 -0
  93. package/lib/types/dsh-adapter/extensions.d.ts +4 -1
  94. package/lib/types/dsh-adapter/extensions.d.ts.map +1 -1
  95. package/lib/types/dsh-adapter/extensions.js +8 -1
  96. package/lib/types/dsh-adapter/index.d.ts +11 -1
  97. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  98. package/lib/types/dsh-adapter/index.js +2 -0
  99. package/lib/types/dsh-adapter/plugin-test-utils.d.ts +62 -0
  100. package/lib/types/dsh-adapter/plugin-test-utils.d.ts.map +1 -0
  101. package/lib/types/dsh-adapter/plugin-test-utils.js +61 -0
  102. package/lib/types/dsh-adapter/plugin.d.ts +20 -0
  103. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  104. package/lib/types/dsh-adapter/plugin.js +174 -39
  105. package/lib/types/dsh-adapter/plugins-info.d.ts.map +1 -1
  106. package/lib/types/dsh-adapter/plugins-info.js +12 -0
  107. package/lib/types/dsh-adapter/preset-resolution.d.ts +49 -0
  108. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -0
  109. package/lib/types/dsh-adapter/preset-resolution.js +42 -0
  110. package/lib/types/dsh-adapter/presets.d.ts +0 -26
  111. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  112. package/lib/types/dsh-adapter/presets.js +4 -14
  113. package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -1
  114. package/lib/types/dsh-adapter/promptDebug.js +4 -1
  115. package/lib/types/dsh-adapter/providerGuard.d.ts +75 -0
  116. package/lib/types/dsh-adapter/providerGuard.d.ts.map +1 -0
  117. package/lib/types/dsh-adapter/providerGuard.js +104 -0
  118. package/lib/types/dsh-adapter/questions-answerer.d.ts +49 -0
  119. package/lib/types/dsh-adapter/questions-answerer.d.ts.map +1 -0
  120. package/lib/types/dsh-adapter/questions-answerer.js +61 -0
  121. package/lib/types/dsh-adapter/recap.d.ts.map +1 -1
  122. package/lib/types/dsh-adapter/recap.js +12 -6
  123. package/lib/types/dsh-adapter/sanitize.d.ts.map +1 -1
  124. package/lib/types/dsh-adapter/sanitize.js +6 -1
  125. package/lib/types/dsh-adapter/sessions/digest.d.ts +21 -1
  126. package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -1
  127. package/lib/types/dsh-adapter/sessions/digest.js +229 -3
  128. package/lib/types/dsh-adapter/sessions/frames.d.ts +3 -0
  129. package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -1
  130. package/lib/types/dsh-adapter/sessions/frames.js +34 -19
  131. package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
  132. package/lib/types/dsh-adapter/sessions/list.js +148 -31
  133. package/lib/types/dsh-adapter/sessions/store.d.ts +8 -0
  134. package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
  135. package/lib/types/dsh-adapter/sessions/store.js +24 -4
  136. package/lib/types/dsh-adapter/sessions/types.d.ts +3 -0
  137. package/lib/types/dsh-adapter/sessions/types.d.ts.map +1 -1
  138. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  139. package/lib/types/dsh-adapter/settings-sections.js +13 -1
  140. package/lib/types/dsh-adapter/settingsEditor.d.ts +6 -0
  141. package/lib/types/dsh-adapter/settingsEditor.d.ts.map +1 -1
  142. package/lib/types/dsh-adapter/settingsEditor.js +15 -3
  143. package/lib/types/dsh-adapter/subagents.d.ts +4 -0
  144. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  145. package/lib/types/dsh-adapter/subagents.js +8 -0
  146. package/lib/types/dsh-adapter/themes.d.ts +51 -0
  147. package/lib/types/dsh-adapter/themes.d.ts.map +1 -0
  148. package/lib/types/dsh-adapter/themes.js +272 -0
  149. package/lib/types/dsh-adapter/toast.d.ts +67 -0
  150. package/lib/types/dsh-adapter/toast.d.ts.map +1 -0
  151. package/lib/types/dsh-adapter/toast.js +141 -0
  152. package/lib/types/dsh-adapter/types.d.ts +1 -1
  153. package/lib/types/extensions.d.ts +4 -0
  154. package/lib/types/extensions.d.ts.map +1 -1
  155. package/lib/types/extensions.js +5 -1
  156. package/lib/types/history.d.ts.map +1 -1
  157. package/lib/types/history.js +7 -2
  158. package/lib/types/hooks/useRuntimeThemeSnapshot.d.ts +10 -0
  159. package/lib/types/hooks/useRuntimeThemeSnapshot.d.ts.map +1 -0
  160. package/lib/types/hooks/useRuntimeThemeSnapshot.js +32 -0
  161. package/lib/types/i18n.d.ts +199 -41
  162. package/lib/types/i18n.d.ts.map +1 -1
  163. package/lib/types/i18n.js +82 -20
  164. package/lib/types/ink/components/App.d.ts +20 -0
  165. package/lib/types/ink/components/App.d.ts.map +1 -1
  166. package/lib/types/ink/components/App.js +142 -13
  167. package/lib/types/ink/components/Box.d.ts +39 -2
  168. package/lib/types/ink/components/Box.d.ts.map +1 -1
  169. package/lib/types/ink/components/Box.js +61 -37
  170. package/lib/types/ink/events/context-menu-event.d.ts +19 -0
  171. package/lib/types/ink/events/context-menu-event.d.ts.map +1 -0
  172. package/lib/types/ink/events/context-menu-event.js +20 -0
  173. package/lib/types/ink/events/drag-event.d.ts +44 -0
  174. package/lib/types/ink/events/drag-event.d.ts.map +1 -0
  175. package/lib/types/ink/events/drag-event.js +39 -0
  176. package/lib/types/ink/events/event-handlers.d.ts +8 -0
  177. package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
  178. package/lib/types/ink/events/event-handlers.js +8 -0
  179. package/lib/types/ink/hit-test.d.ts +69 -0
  180. package/lib/types/ink/hit-test.d.ts.map +1 -1
  181. package/lib/types/ink/hit-test.js +260 -6
  182. package/lib/types/ink/ink.d.ts +25 -0
  183. package/lib/types/ink/ink.d.ts.map +1 -1
  184. package/lib/types/ink/ink.js +74 -10
  185. package/lib/types/ink/parse-keypress.d.ts +20 -0
  186. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  187. package/lib/types/ink/parse-keypress.js +190 -22
  188. package/lib/types/ink/renderer.d.ts.map +1 -1
  189. package/lib/types/ink/renderer.js +15 -1
  190. package/lib/types/ink/screen.d.ts +7 -0
  191. package/lib/types/ink/screen.d.ts.map +1 -1
  192. package/lib/types/ink/screen.js +8 -1
  193. package/lib/types/ink/termio/osc.d.ts +2 -1
  194. package/lib/types/ink/termio/osc.d.ts.map +1 -1
  195. package/lib/types/ink/termio/osc.js +26 -5
  196. package/lib/types/plugin-spec/registry.d.ts +1 -1
  197. package/lib/types/plugin-spec/registry.js +1 -1
  198. package/lib/types/presetPrefs.d.ts +6 -5
  199. package/lib/types/presetPrefs.d.ts.map +1 -1
  200. package/lib/types/presetPrefs.js +10 -5
  201. package/lib/types/screens/Chat.d.ts +4 -1
  202. package/lib/types/screens/Chat.d.ts.map +1 -1
  203. package/lib/types/screens/Chat.js +169 -68
  204. package/lib/types/screens/SessionBrowser.d.ts +9 -4
  205. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  206. package/lib/types/screens/SessionBrowser.js +483 -75
  207. package/lib/types/screens/SessionTree.d.ts.map +1 -1
  208. package/lib/types/screens/SessionTree.js +5 -1
  209. package/lib/types/screens/Settings.d.ts.map +1 -1
  210. package/lib/types/screens/Settings.js +5 -1
  211. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  212. package/lib/types/screens/StatusLine.js +5 -1
  213. package/lib/types/screens/chatOverlay.d.ts +3 -2
  214. package/lib/types/screens/chatOverlay.d.ts.map +1 -1
  215. package/lib/types/screens/chatOverlay.js +2 -0
  216. package/lib/types/sessionPins.d.ts +33 -0
  217. package/lib/types/sessionPins.d.ts.map +1 -0
  218. package/lib/types/sessionPins.js +181 -0
  219. package/lib/types/sessions/format.d.ts +2 -0
  220. package/lib/types/sessions/format.d.ts.map +1 -1
  221. package/lib/types/sessions/format.js +9 -0
  222. package/lib/types/sessions/view.d.ts +42 -6
  223. package/lib/types/sessions/view.d.ts.map +1 -1
  224. package/lib/types/sessions/view.js +133 -35
  225. package/lib/types/test-utils.d.ts +2 -0
  226. package/lib/types/test-utils.d.ts.map +1 -0
  227. package/lib/types/test-utils.js +8 -0
  228. package/lib/types/theme.d.ts +22 -5
  229. package/lib/types/theme.d.ts.map +1 -1
  230. package/lib/types/theme.js +57 -7
  231. package/lib/types/themeCatalog.d.ts +36 -0
  232. package/lib/types/themeCatalog.d.ts.map +1 -0
  233. package/lib/types/themeCatalog.js +157 -0
  234. package/lib/types/tips.d.ts.map +1 -1
  235. package/lib/types/tips.js +114 -18
  236. package/lib/types/update.d.ts +159 -3
  237. package/lib/types/update.d.ts.map +1 -1
  238. package/lib/types/update.js +497 -16
  239. package/lib/types/utils/debug.d.ts.map +1 -1
  240. package/lib/types/utils/debug.js +2 -1
  241. package/lib/types/utils/keymap.d.ts +1 -1
  242. package/lib/types/utils/keymap.d.ts.map +1 -1
  243. package/lib/types/utils/keymap.js +19 -2
  244. package/lib/types/utils/urlGuard.d.ts +28 -0
  245. package/lib/types/utils/urlGuard.d.ts.map +1 -0
  246. package/lib/types/utils/urlGuard.js +21 -0
  247. package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +3 -0
  248. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.d.ts +7 -11
  249. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.d.ts +16 -1
  250. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.js +23 -2
  251. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.d.ts +5 -5
  252. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/pi-ai.d.ts +23 -0
  253. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/pi-ai.js +27 -0
  254. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.d.ts +17 -3
  255. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.js +36 -4
  256. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +11 -12
  257. package/package.json +114 -74
  258. package/lib/types/dsh-adapter/packaged-skills.d.ts +0 -19
  259. package/lib/types/dsh-adapter/packaged-skills.d.ts.map +0 -1
  260. package/lib/types/dsh-adapter/packaged-skills.js +0 -78
  261. package/skills/audit/SKILL.md +0 -24
  262. package/skills/bug/SKILL.md +0 -27
  263. package/skills/pr-comments/SKILL.md +0 -20
  264. package/skills/practice/SKILL.md +0 -22
  265. package/skills/release-notes/SKILL.md +0 -25
  266. package/skills/review/SKILL.md +0 -25
  267. package/skills/vuln-check/SKILL.md +0 -25
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantThinkingMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantThinkingMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAczB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAUjE,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAA;IAChB;;4CAEwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;8EAE0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAClC,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAiB,EACjB,OAAe,EACf,UAAU,EACV,UAAkB,EAClB,OAAO,GACR,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAkIzB"}
1
+ {"version":3,"file":"AssistantThinkingMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantThinkingMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAczB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAUjE,KAAK,KAAK,GAAG;IACX,QAAQ,EAAE,MAAM,CAAA;IAChB;;;sDAGkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAA;IAChB;;4CAEwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;8EAE0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAClC,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAiB,EACjB,OAAe,EACf,UAAU,EACV,UAAkB,EAClB,OAAO,GACR,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAuIzB"}
@@ -17,17 +17,21 @@ import { isMinimalMode } from '../../minimalMode.js';
17
17
  * to exactly this many rows, keeps the block height stream-independent. */
18
18
  const PREVIEW_ROWS = 3;
19
19
  /**
20
- * Thinking block: folded `⚓ Thinking (ctrl+o to expand)`, expanded shows the
21
- * full reasoning text indented under `⚓ Thinking…`. While the reasoning
22
- * streams the leading mark is a rotating braille spinner (`⠋⠙⠹…`, Kimi Code
23
- * style), settling back to the static anchor (`⚓`) once the turn ends. When
20
+ * Thinking block: settled rows fold to `⚓ Thinking (ctrl+o to expand)`;
21
+ * streaming rows switch between a three-line preview and the full reasoning
22
+ * text on click. The live leading mark is a rotating braille spinner
23
+ * (`⠋⠙⠹…`, Kimi Code style), settling back to the static anchor (`⚓`). When
24
24
  * the channel records the reasoning duration, the label carries it
25
25
  * (`⚓ Thinking · 12s …`) — dsh-tui's take on making thinking time visible in
26
26
  * the transcript.
27
27
  */
28
- export function AssistantThinkingMessage({ thinking, addMargin, verbose, streaming = false, preview = false, durationMs, isSelected = false, onClick, }) {
28
+ export function AssistantThinkingMessage({ thinking, textFull, addMargin, verbose, streaming = false, preview = false, durationMs, isSelected = false, onClick, }) {
29
29
  if (!thinking)
30
30
  return null;
31
+ // The preview ticker tracks the newest ARRIVED line (smooth streaming must
32
+ // not lag it behind the reveal); the expanded body below paints `thinking`
33
+ // — the revealed slice under smooth streaming, the full text otherwise.
34
+ const tickerText = textFull ?? thinking;
31
35
  // Spinner frame (80ms cadence, only while the reasoning is still
32
36
  // streaming — same pattern as BtwPanel's answering spinner).
33
37
  const [frame, setFrame] = React.useState(0);
@@ -64,7 +68,7 @@ export function AssistantThinkingMessage({ thinking, addMargin, verbose, streami
64
68
  // from the start (leading ellipsis) so the newest tokens — which grow
65
69
  // at the line's end — stay visible while the line is longer than the
66
70
  // width.
67
- const lines = thinking.split('\n');
71
+ const lines = tickerText.split('\n');
68
72
  const visible = lines.slice(-PREVIEW_ROWS);
69
73
  const clipped = lines.length > visible.length;
70
74
  // Pad with single spaces — an empty-string Text renders with zero
@@ -45,6 +45,18 @@ type Props = {
45
45
  * `+N lines` hint; verbose/expanded cards render the full title.
46
46
  */
47
47
  foldTerminalCommand?: boolean;
48
+ /**
49
+ * Smooth streaming reveal (settings `dsh-tui.smoothStreaming`): the card
50
+ * BODY (diff hunks / write content — model-authored prose, not tool
51
+ * output) paints through an even ~30fps line reveal when it first appears,
52
+ * instead of one jarring block. Only the pending CALL view animates; the
53
+ * settled result view paints complete (real output is progress, not
54
+ * prose), and so do replayed cards.
55
+ */
56
+ smoothReveal?: boolean;
57
+ /** Live-arrived row (channel `fresh`): gates reveal participation —
58
+ * replayed cards must paint complete. */
59
+ fresh?: boolean;
48
60
  };
49
61
  export declare function toolNameColor(raw: string): keyof Theme;
50
62
  /**
@@ -53,6 +65,6 @@ export declare function toolNameColor(raw: string): keyof Theme;
53
65
  * output, read content — instead of the raw result dump (mirroring Claude Code's `AssistantToolUseMessage.tsx` + the dsh-tools presentation views the
54
66
  * channel captures per call).
55
67
  */
56
- export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, onClick, footnote, diffLayout, toolBackground, onOpenFile, foldTerminalCommand, }: Props): React.ReactNode;
68
+ export declare function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected, isExpanded, onClick, footnote, diffLayout, toolBackground, onOpenFile, foldTerminalCommand, smoothReveal, fresh, }: Props): React.ReactNode;
57
69
  export {};
58
70
  //# sourceMappingURL=AssistantToolUseMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAKvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,2EAA2E;IAC3E,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B,CAAA;AAsFD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,CAKtD;AAuOD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,OAAO,EACP,QAAQ,EACR,UAAmB,EACnB,cAAuB,EACvB,UAAU,EACV,mBAA2B,GAC5B,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA6MzB"}
1
+ {"version":3,"file":"AssistantToolUseMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/AssistantToolUseMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAA8C,OAAO,EAAE,MAAM,8BAA8B,CAAA;AAMvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAGjE,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,OAAO,CAAA;IACb,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAA;IAClB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;OAIG;IACH,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;IACzC,2EAA2E;IAC3E,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;8CAC0C;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAsFD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,KAAK,CAKtD;AA+OD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAkB,EAClB,UAAkB,EAClB,OAAO,EACP,QAAQ,EACR,UAAmB,EACnB,cAAuB,EACvB,UAAU,EACV,mBAA2B,EAC3B,YAAoB,EACpB,KAAa,GACd,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAiOzB"}
@@ -7,7 +7,9 @@ import { useAnimationFrame } from '../../ink/hooks/use-animation-frame.js';
7
7
  import { ToolUseLoader } from '../ToolUseLoader.js';
8
8
  import { SplitDiffView } from '../SplitDiffView.js';
9
9
  import { SyntaxText } from '../SyntaxText.js';
10
+ import { useTooltip } from '../Tooltip.js';
10
11
  import { formatDuration } from '../../cc/format.js';
12
+ import { getRevealVersion, revealLinesOf, snapReveal, subscribeReveal } from '../smoothReveal.js';
11
13
  /** Tool display names: DSH emits lowercase tool ids (`bash`); Claude Code
12
14
  * shows capitalized names (`Bash`). Map the common ones, fall back to the
13
15
  * id with its first letter uppercased. */
@@ -223,11 +225,21 @@ function foldTerminalTitle(title) {
223
225
  return { first, hidden };
224
226
  }
225
227
  function HeaderTitle({ name, title, isTerminal, folded, displayArgs, argsLanguage, nameColor, filePath, onOpenFile }) {
228
+ // Hover tooltip: the header line truncates long paths/commands, so the
229
+ // full string (or the unfolded terminal script) pops up after a dwell.
230
+ // Empty content is a no-op inside the hook.
231
+ const headerTooltip = useTooltip(() => {
232
+ if (title === undefined)
233
+ return displayArgs;
234
+ if (isTerminal)
235
+ return title;
236
+ return title.trim();
237
+ });
226
238
  if (title === undefined) {
227
- return (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: name }) }), displayArgs !== '' && (_jsxs(Box, { flexWrap: "nowrap", children: [_jsx(Text, { children: "(" }), _jsx(SyntaxText, { text: clipHeaderArgs(displayArgs), sourceText: displayArgs, language: argsLanguage }), _jsx(Text, { children: ")" })] }))] }));
239
+ return (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: name }) }), displayArgs !== '' && (_jsxs(Box, { flexWrap: "nowrap", ...headerTooltip, children: [_jsx(Text, { children: "(" }), _jsx(SyntaxText, { text: clipHeaderArgs(displayArgs), sourceText: displayArgs, language: argsLanguage }), _jsx(Text, { children: ")" })] }))] }));
228
240
  }
229
241
  if (isTerminal) {
230
- return (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: name }) }), _jsx(Box, { flexWrap: "nowrap", children: folded === undefined ? (_jsxs(Text, { children: ["(", title, ")"] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["(", folded.first, ")"] }), _jsx(Text, { dimColor: true, children: ` … +${folded.hidden} lines (ctrl+o to expand)` })] })) })] }));
242
+ return (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: name }) }), _jsx(Box, { flexWrap: "nowrap", ...headerTooltip, children: folded === undefined ? (_jsxs(Text, { children: ["(", title, ")"] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["(", folded.first, ")"] }), _jsx(Text, { dimColor: true, children: ` … +${folded.hidden} lines (ctrl+o to expand)` })] })) })] }));
231
243
  }
232
244
  const trimmed = title.trim();
233
245
  if (trimmed === '') {
@@ -242,7 +254,7 @@ function HeaderTitle({ name, title, isTerminal, folded, displayArgs, argsLanguag
242
254
  const at = trimmed.indexOf(filePath);
243
255
  const before = trimmed.slice(0, at);
244
256
  const after = trimmed.slice(at + filePath.length);
245
- return (_jsxs(Box, { flexWrap: "nowrap", children: [_jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: before }), _jsx(Box, { onClick: (event) => {
257
+ return (_jsxs(Box, { flexWrap: "nowrap", ...headerTooltip, children: [_jsx(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: before }), _jsx(Box, { onClick: (event) => {
246
258
  event.stopImmediatePropagation();
247
259
  onOpenFile(filePath);
248
260
  }, children: _jsx(Text, { underline: true, wrap: "truncate-end", children: filePath }) }), after !== '' && (_jsx(Text, { bold: false, color: "text", wrap: "truncate-end", children: after }))] }));
@@ -250,7 +262,7 @@ function HeaderTitle({ name, title, isTerminal, folded, displayArgs, argsLanguag
250
262
  const space = trimmed.indexOf(' ');
251
263
  const head = space === -1 ? trimmed : trimmed.slice(0, space);
252
264
  const tail = space === -1 ? '' : trimmed.slice(space);
253
- return (_jsx(Box, { flexWrap: "nowrap", children: _jsxs(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: [head, _jsx(Text, { bold: false, color: "text", children: tail })] }) }));
265
+ return (_jsx(Box, { flexWrap: "nowrap", ...headerTooltip, children: _jsxs(Text, { bold: true, color: nameColor, wrap: "truncate-end", children: [head, _jsx(Text, { bold: false, color: "text", children: tail })] }) }));
254
266
  }
255
267
  /**
256
268
  * Tool-call card: `● Edit /path` header with a blinking status dot, then the
@@ -258,7 +270,11 @@ function HeaderTitle({ name, title, isTerminal, folded, displayArgs, argsLanguag
258
270
  * output, read content — instead of the raw result dump (mirroring Claude Code's `AssistantToolUseMessage.tsx` + the dsh-tools presentation views the
259
271
  * channel captures per call).
260
272
  */
261
- export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected = false, isExpanded = false, onClick, footnote, diffLayout = 'auto', toolBackground = 'none', onOpenFile, foldTerminalCommand = false, }) {
273
+ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected = false, isExpanded = false, onClick, footnote, diffLayout = 'auto', toolBackground = 'none', onOpenFile, foldTerminalCommand = false, smoothReveal = false, fresh = false, }) {
274
+ // Reveal-frame wakeups: the shared scheduler bumps its version, which
275
+ // re-renders this card even while MemoRow props hold still (the reveal
276
+ // lives outside React — see smoothReveal.ts).
277
+ React.useSyncExternalStore(subscribeReveal, getRevealVersion);
262
278
  const isRunning = tool.status === 'running';
263
279
  const isError = tool.status === 'error';
264
280
  const displayArgs = verbose ? tool.argsFull ?? tool.argsText : tool.argsText;
@@ -323,6 +339,21 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
323
339
  // long error body must not be the reason it disappears.
324
340
  const lines = capLines(body, cap, verbose);
325
341
  const rendered = footnote === undefined ? lines : [...lines, { text: footnote, tone: 'hint' }];
342
+ // Smooth reveal (line-unit, pending CALL body only): model-authored prose
343
+ // (diff hunks, write content) flows in at ~30fps; the settled RESULT view,
344
+ // error bodies, verbose/expanded cards, and replayed (non-fresh) cards all
345
+ // paint complete. `snapReveal` on every non-revealable render retires a
346
+ // cursor the moment its card stops qualifying (result arrived, user
347
+ // expanded) — idempotent, safe during render.
348
+ const revealKey = `tool:${tool.callId}`;
349
+ const revealable = smoothReveal && !isError && isRunning && view !== undefined &&
350
+ tool.resultView === undefined && !verbose && !isExpanded && fresh;
351
+ if (!revealable)
352
+ snapReveal(revealKey);
353
+ const revealedLineCount = revealable
354
+ ? revealLinesOf(revealKey, rendered.length, { enabled: true, active: true })
355
+ : rendered.length;
356
+ const shownLines = revealedLineCount >= rendered.length ? rendered : rendered.slice(0, revealedLineCount);
326
357
  // Nested split-diff context panes must also yield to interaction highlights.
327
358
  // `none` leaves them transparent so the selected/expanded root shows through.
328
359
  const ordinaryToolBackground = isSelected || isExpanded ? 'none' : toolBackground;
@@ -344,7 +375,7 @@ export function AssistantToolUseMessage({ tool, addMargin, verbose, isSelected =
344
375
  return (_jsx(Box, { ref: viewportRef, flexDirection: "row", justifyContent: "space-between", marginTop: addMargin ? 1 : 0, width: "100%", onClick: onClick,
345
376
  // Only selection paints a highlight; the configured treatment applies
346
377
  // to an ordinary card. Diff line tints stay - they are content, not chrome.
347
- backgroundColor: isSelected ? 'messageActionsBackground' : hoverTint ? 'toolCardBackground' : ordinaryBackground, onMouseEnter: interactive ? () => setHovered(true) : undefined, onMouseLeave: interactive ? () => setHovered(false) : undefined, children: _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", minWidth: minWidth, children: [_jsx(ToolUseLoader, { shouldAnimate: isRunning, isUnresolved: isRunning, isError: isError, toolName: tool.name }), _jsx(HeaderTitle, { name: name, title: headerTitle, isTerminal: headerIsTerminal, folded: foldedHeader, displayArgs: displayArgs, argsLanguage: argsLanguage, nameColor: toolNameColor(tool.name), filePath: filePath, onOpenFile: onOpenFile }), !isRunning && (_jsx(Box, { flexWrap: "nowrap", children: _jsx(Text, { dimColor: !hovered, children: elapsedText }) })), hovered && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: isExpanded ? '▴' : '▾' }) }))] }), useSplitDiff && view?.card === 'diff' ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_FIRST }) }), _jsx(SplitDiffView, { diffs: view.diffs, width: columns - 4, maxRows: DIFF_BODY_MAX_LINES, verbose: verbose, toolBackground: ordinaryToolBackground })] })) : (rendered.map((line, index) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { color: line.tone === 'add'
378
+ backgroundColor: isSelected ? 'messageActionsBackground' : hoverTint ? 'toolCardBackground' : ordinaryBackground, onMouseEnter: interactive ? () => setHovered(true) : undefined, onMouseLeave: interactive ? () => setHovered(false) : undefined, children: _jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsxs(Box, { flexDirection: "row", flexWrap: "nowrap", minWidth: minWidth, children: [_jsx(ToolUseLoader, { shouldAnimate: isRunning, isUnresolved: isRunning, isError: isError, toolName: tool.name }), _jsx(HeaderTitle, { name: name, title: headerTitle, isTerminal: headerIsTerminal, folded: foldedHeader, displayArgs: displayArgs, argsLanguage: argsLanguage, nameColor: toolNameColor(tool.name), filePath: filePath, onOpenFile: onOpenFile }), !isRunning && (_jsx(Box, { flexWrap: "nowrap", children: _jsx(Text, { dimColor: !hovered, children: elapsedText }) })), hovered && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, children: isExpanded ? '▴' : '▾' }) }))] }), useSplitDiff && view?.card === 'diff' ? (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: GUTTER_FIRST }) }), _jsx(SplitDiffView, { diffs: view.diffs, width: columns - 4, maxRows: DIFF_BODY_MAX_LINES, verbose: verbose, toolBackground: ordinaryToolBackground, reveal: revealable ? { key: `${revealKey}:split` } : undefined })] })) : (shownLines.map((line, index) => (_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 3, flexShrink: 0, children: _jsx(Text, { color: line.tone === 'add'
348
379
  ? 'diffAddedWord'
349
380
  : line.tone === 'del'
350
381
  ? 'diffRemovedWord'
@@ -1 +1 @@
1
- {"version":3,"file":"UserPromptMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/UserPromptMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAA;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAClC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,SAAS,EACT,UAAkB,EAClB,OAAO,GACR,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CA6BzB"}
1
+ {"version":3,"file":"UserPromptMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/messages/UserPromptMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAA;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAClC,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,SAAS,EACT,UAAkB,EAClB,OAAO,GACR,EAAE,KAAK,GAAG,KAAK,CAAC,SAAS,CAmCzB"}
@@ -3,6 +3,7 @@ import { Box, Text, useTerminalSize } from '../../ui.js';
3
3
  import { POINTER } from '../../cc/figures.js';
4
4
  import { stringWidth } from '../../ink/stringWidth.js';
5
5
  import { wrapWidth } from '../../sessions/format.js';
6
+ import { useTooltip } from '../Tooltip.js';
6
7
  /**
7
8
  * User prompt bubble: `❯ text` in bold briefLabelYou gold with no background
8
9
  * fill (Kimi Code style: the user turn gets a distinct bold tint so it reads
@@ -20,5 +21,10 @@ export function UserPromptMessage({ text, addMargin, isSelected = false, onClick
20
21
  // Ink; a second wrap would move the continuation back to column zero.
21
22
  const lines = wrapWidth(text, Math.max(1, columns - prefixWidth - 3));
22
23
  const continuationIndent = ' '.repeat(prefixWidth);
23
- return (_jsx(Box, { flexDirection: "column", marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, paddingRight: 1, onClick: onClick, children: lines.map((line, index) => (_jsxs(Text, { color: "briefLabelYou", bold: true, wrap: "truncate-end", children: [index === 0 ? `${POINTER} ` : continuationIndent, line] }, index))) }));
24
+ // Hover tooltip: a message that wrapped/truncated onto several visual
25
+ // lines pops its full original text in one floating card (the pointer
26
+ // row only shows one visual line at a time).
27
+ const promptTooltip = useTooltip(text);
28
+ const tooltipActive = lines.length > 1;
29
+ return (_jsx(Box, { flexDirection: "column", marginTop: addMargin ? 1 : 0, backgroundColor: isSelected ? 'messageActionsBackground' : undefined, paddingRight: 1, onClick: onClick, ...(tooltipActive ? promptTooltip : {}), children: lines.map((line, index) => (_jsxs(Text, { color: "briefLabelYou", bold: true, wrap: "truncate-end", children: [index === 0 ? `${POINTER} ` : continuationIndent, line] }, index))) }));
24
30
  }
@@ -341,5 +341,5 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
341
341
  ...(onBack === undefined ? [] : [t('question-hint-cancel')]),
342
342
  ...(multiSelect && checked.size > 0 ? [t('question-hint-selected', { n: checked.size })] : []),
343
343
  ];
344
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: headerTitle, padding: 4 }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [question.header !== undefined && (_jsxs(Text, { color: "suggestion", bold: true, children: ["\u25C8 ", question.header] })), _jsx(Text, { bold: true, wrap: "wrap", children: question.question }), question.detail !== undefined && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: question.detail.split('\n').map((line, index) => (_jsx(Text, { dimColor: true, italic: true, wrap: "wrap", children: line }, index))) }))] }), renderOptions(), error !== null && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "error", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: hintParts.join(' · ') }) })] }));
344
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: headerTitle }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [question.header !== undefined && (_jsxs(Text, { color: "suggestion", bold: true, children: ["\u25C8 ", question.header] })), _jsx(Text, { bold: true, wrap: "wrap", children: question.question }), question.detail !== undefined && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: question.detail.split('\n').map((line, index) => (_jsx(Text, { dimColor: true, italic: true, wrap: "wrap", children: line }, index))) }))] }), renderOptions(), error !== null && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "error", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: hintParts.join(' · ') }) })] }));
345
345
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PlanReviewPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/PlanReviewPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAKvE,MAAM,MAAM,oBAAoB,GAAG;IACjC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAC7E,CAAA;IACD,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACzD,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAC9B,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,QAAQ,EACR,QAAQ,GACT,EAAE,oBAAoB,GAAG,KAAK,CAAC,SAAS,CAmQxC"}
1
+ {"version":3,"file":"PlanReviewPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/PlanReviewPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAOzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAKvE,MAAM,MAAM,oBAAoB,GAAG;IACjC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAC7E,CAAA;IACD,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACzD,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAC9B,CAAA;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,QAAQ,EACR,QAAQ,GACT,EAAE,oBAAoB,GAAG,KAAK,CAAC,SAAS,CAiSxC"}
@@ -17,7 +17,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
17
17
  */
18
18
  import React from 'react';
19
19
  import { t } from '../../i18n.js';
20
- import { Box, Text, useInput } from '../../ui.js';
20
+ import { Box, Text, useInput, ScrollBox, useTerminalSize } from '../../ui.js';
21
21
  import { useDeclaredCursor } from '../../ink/hooks/use-declared-cursor.js';
22
22
  import { Divider } from '../design-system/Divider.js';
23
23
  import { Markdown } from '../Markdown.js';
@@ -34,6 +34,23 @@ export function PlanReviewPanel({ question, onAnswer, onCancel, }) {
34
34
  const [feedback, setFeedback] = React.useState('');
35
35
  const [cursor, setCursor] = React.useState(0);
36
36
  const [error, setError] = React.useState(null);
37
+ const { rows: terminalRows } = useTerminalSize();
38
+ // Chat chrome (status line) + panel scaffolding (divider, question,
39
+ // spacings, hint) consume twelve rows before the plan body — same
40
+ // constant as AskUserQuestionPanel. Option/feedback/error rows are
41
+ // charged on top so a long markdown detail cannot push them off-screen.
42
+ // A definite `height` (not just maxHeight) is required: otherwise the
43
+ // ScrollBox grows with the markdown and scrollBy is a no-op.
44
+ const optionRows = options.reduce((sum, option) => sum + (option.description === undefined ? 1 : 2), 0) + 1 /* extra gap on the focused option */ + 1; /* feedback row */
45
+ const reservedRows = 12 + optionRows + (error === null ? 0 : 2);
46
+ // Floor at zero, not four: on a short terminal the decision rows fill the
47
+ // budget and no plan-body rows remain. Forcing four here would re-inflate
48
+ // the panel past the viewport and push the controls off-screen again — the
49
+ // exact regression this panel exists to prevent. When nothing remains, the
50
+ // body viewport is omitted entirely (a zero-height ScrollBox is neither
51
+ // readable nor a useful wheel target).
52
+ const detailMax = Math.max(0, terminalRows - reservedRows);
53
+ const detailScrollRef = React.useRef(null);
37
54
  const inputFocused = focusIndex === options.length;
38
55
  // Park the native terminal cursor on the feedback caret so IME preedit
39
56
  // (pinyin) renders inline at the input instead of the screen's bottom row
@@ -87,7 +104,16 @@ export function PlanReviewPanel({ question, onAnswer, onCancel, }) {
87
104
  const text = feedback.trim();
88
105
  onAnswer({ selected: declineSelected(), ...(text !== '' ? { custom: text } : {}) });
89
106
  };
90
- useInput((input, key) => {
107
+ useInput((input, key, event) => {
108
+ // Steal the wheel while this panel is mounted so Chat's fallback cannot
109
+ // scroll the transcript. Position-first already hits the ScrollBox when
110
+ // the pointer is over it; this covers the option/hint rows and any
111
+ // dispatchWheelAt miss. Up/Down stay on the decision rows.
112
+ if (key.wheelUp || key.wheelDown) {
113
+ detailScrollRef.current?.scrollBy(key.wheelUp ? -3 : 3);
114
+ event.stopImmediatePropagation();
115
+ return;
116
+ }
91
117
  if (key.escape || (key.ctrl && input === 'c')) {
92
118
  onCancel();
93
119
  return;
@@ -179,7 +205,7 @@ export function PlanReviewPanel({ question, onAnswer, onCancel, }) {
179
205
  setFocusIndex(options.length);
180
206
  setError(null);
181
207
  };
182
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: ` ${question.header ?? t('plan-review-fallback-header')} `, padding: 4 }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, wrap: "wrap", children: question.question }), question.detail !== undefined && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Markdown, { children: question.detail }) }))] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.map((option, index) => {
208
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: ` ${question.header ?? t('plan-review-fallback-header')} ` }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, wrap: "wrap", children: question.question }), question.detail !== undefined && detailMax > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, height: detailMax, flexShrink: 0, children: _jsx(ScrollBox, { ref: detailScrollRef, flexDirection: "column", flexGrow: 1, height: detailMax, children: _jsx(Markdown, { children: question.detail }) }) }))] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [options.map((option, index) => {
183
209
  const focused = index === focusIndex;
184
210
  const isApprove = option.label === approveLabel;
185
211
  return (_jsxs(Box, { flexDirection: "row", marginTop: focused ? 1 : 0, onClick: () => clickOption(index), onMouseEnter: () => setHoverIndex(index), onMouseLeave: () => setHoverIndex(current => (current === index ? -1 : current)), backgroundColor: hoverIndex === index && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: focused ? 'claude' : undefined, bold: focused, children: focused ? POINTER : ' ' }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsxs(Text, { bold: focused, color: focused || isApprove ? 'claude' : undefined, wrap: "wrap", children: [index + 1, ". ", option.label] }), option.description !== undefined && (_jsx(Text, { dimColor: true, wrap: "wrap", children: option.description }))] })] }, option.label));
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { ClickEvent } from '../../ink/events/click-event.js';
3
+ import type { ContextMenuEvent } from '../../ink/events/context-menu-event.js';
3
4
  import type { SessionSummary } from '../../dsh-adapter/sessions/index.js';
4
5
  /**
5
6
  * One session in the browser's list: a title line and a metadata line.
@@ -14,16 +15,22 @@ import type { SessionSummary } from '../../dsh-adapter/sessions/index.js';
14
15
  * stay exactly two lines at every terminal width, and a row that wraps
15
16
  * destroys the alignment that lets the eye scan a list at all.
16
17
  */
17
- export declare function SessionListRow({ session, width, depth, focused, now, onClick, }: {
18
+ export declare function SessionListRow({ session, width, depth, focused, pinned, now, onClick, onContextMenu, onTogglePin, }: {
18
19
  session: SessionSummary;
19
20
  /** Columns available to the row, indentation included. */
20
21
  width: number;
21
22
  /** 0 for a conversation, 1 for a sub-agent run under its parent. */
22
23
  depth: number;
23
24
  focused: boolean;
25
+ /** Whether the user pinned this session to the top of the browser. */
26
+ pinned: boolean;
24
27
  /** Epoch ms used for every relative time in this render pass. */
25
28
  now: number;
26
29
  /** 鼠标点击行(fullscreen):恢复该会话(与 Enter 同路径)。 */
27
30
  onClick?(event: ClickEvent): void;
31
+ /** 鼠标右键(fullscreen):在该行弹出操作菜单(打开/固定/重命名/删除)。 */
32
+ onContextMenu?(event: ContextMenuEvent): void;
33
+ /** 点击行内 ★/☆(fullscreen):切换固定状态,不冒泡成"打开会话"。 */
34
+ onTogglePin?(): void;
28
35
  }): React.ReactNode;
29
36
  //# sourceMappingURL=SessionListRow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionListRow.d.ts","sourceRoot":"","sources":["../../../../src/components/sessions/SessionListRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAQjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAEzE;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,GAAG,EACH,OAAO,GACR,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,4CAA4C;IAC5C,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAClC,GAAG,KAAK,CAAC,SAAS,CA4ClB"}
1
+ {"version":3,"file":"SessionListRow.d.ts","sourceRoot":"","sources":["../../../../src/components/sessions/SessionListRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAA;AAS9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAEzE;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,MAAM,EACN,GAAG,EACH,OAAO,EACP,aAAa,EACb,WAAW,GACZ,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,sEAAsE;IACtE,MAAM,EAAE,OAAO,CAAA;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,4CAA4C;IAC5C,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACjC,gDAAgD;IAChD,aAAa,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC7C,8CAA8C;IAC9C,WAAW,CAAC,IAAI,IAAI,CAAA;CACrB,GAAG,KAAK,CAAC,SAAS,CA8DlB"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
3
  import { Box, Text } from '../../ui.js';
4
4
  import { t } from '../../i18n.js';
5
+ import { useTooltip } from '../Tooltip.js';
5
6
  import { formatBytes, formatWhen, kindMark, titleColor, truncateWidth, } from '../../sessions/format.js';
6
7
  /**
7
8
  * One session in the browser's list: a title line and a metadata line.
@@ -16,12 +17,13 @@ import { formatBytes, formatWhen, kindMark, titleColor, truncateWidth, } from '.
16
17
  * stay exactly two lines at every terminal width, and a row that wraps
17
18
  * destroys the alignment that lets the eye scan a list at all.
18
19
  */
19
- export function SessionListRow({ session, width, depth, focused, now, onClick, }) {
20
+ export function SessionListRow({ session, width, depth, focused, pinned, now, onClick, onContextMenu, onTogglePin, }) {
20
21
  const indent = depth * 2;
21
22
  // Two cells for the focus marker, plus the indent for a nested run.
22
23
  const body = Math.max(8, width - 2 - indent);
23
24
  const mark = kindMark(session.kind);
24
25
  const [hovered, setHovered] = useState(false);
26
+ const pinTooltip = useTooltip(t(pinned ? 'resume-menu-unpin' : 'resume-menu-pin'));
25
27
  const facts = [formatWhen(session.updatedAt, now)];
26
28
  if (session.branch !== undefined)
27
29
  facts.push(session.branch);
@@ -33,5 +35,10 @@ export function SessionListRow({ session, width, depth, focused, now, onClick, }
33
35
  if (session.childCount > 0 && depth === 0) {
34
36
  facts.push(t('session-children', { n: session.childCount }));
35
37
  }
36
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, onClick: onClick, onMouseEnter: onClick !== undefined ? () => setHovered(true) : undefined, onMouseLeave: onClick !== undefined ? () => setHovered(false) : undefined, backgroundColor: hovered && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { children: [_jsx(Text, { color: focused ? 'suggestion' : 'subtle', children: `${' '.repeat(indent)}${focused ? '❯ ' : ' '}` }), mark !== undefined && _jsx(Text, { color: mark.color, children: `${mark.glyph} ` }), _jsx(Text, { color: titleColor(session.title.source, focused), bold: focused, children: truncateWidth(session.label ?? session.title.text, body - (mark === undefined ? 0 : 2)) })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${' '.repeat(indent + 2)}${truncateWidth(facts.join(' · '), body)}` }) })] }));
38
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, onClick: onClick, onContextMenu: onContextMenu, onMouseEnter: onClick !== undefined || onContextMenu !== undefined ? () => setHovered(true) : undefined, onMouseLeave: onClick !== undefined || onContextMenu !== undefined ? () => setHovered(false) : undefined, backgroundColor: focused || hovered ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { children: [_jsx(Text, { color: focused ? 'suggestion' : 'subtle', children: `${' '.repeat(indent)}${focused ? '❯ ' : ' '}` }), _jsx(Box, { ...pinTooltip, onClick: onTogglePin === undefined ? undefined : (event) => {
39
+ // The star is a control on the row, not the row: a click here
40
+ // toggles the pin and must never fall through to resume.
41
+ event.stopImmediatePropagation();
42
+ onTogglePin();
43
+ }, children: _jsx(Text, { color: pinned ? 'remember' : undefined, dimColor: !pinned, children: pinned ? '★ ' : '☆ ' }) }), mark !== undefined && _jsx(Text, { color: mark.color, children: `${mark.glyph} ` }), _jsx(Text, { color: titleColor(session.title.source, focused), bold: focused, children: truncateWidth(session.label ?? session.title.text, body - 2 - (mark === undefined ? 0 : 2)) })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${' '.repeat(indent + 2)}${truncateWidth(facts.join(' · '), body)}` }) })] }));
37
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SessionPreview.d.ts","sourceRoot":"","sources":["../../../../src/components/sessions/SessionPreview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAQvF;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,OAAO,EACP,OAAO,EACP,KAAK,EACL,MAAM,EACN,IAAI,EACJ,GAAG,GACJ,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,KAAK,CAAC,SAAS,CA8DlB"}
1
+ {"version":3,"file":"SessionPreview.d.ts","sourceRoot":"","sources":["../../../../src/components/sessions/SessionPreview.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAQvF;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,OAAO,EACP,OAAO,EACP,KAAK,EACL,MAAM,EACN,IAAI,EACJ,GAAG,GACJ,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,OAAO,EAAE,SAAS,YAAY,EAAE,CAAA;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,KAAK,CAAC,SAAS,CA2ElB"}
@@ -21,7 +21,9 @@ const ROLE = {
21
21
  * session is the same amount of work as on a 40 KB one.
22
22
  */
23
23
  export function SessionPreview({ session, entries, loading, width, height, home, now, }) {
24
- const body = Math.max(8, width - 2);
24
+ const framed = width >= 24 && height >= 4;
25
+ const body = Math.max(8, width - (framed ? 4 : 2));
26
+ const bodyHeight = Math.max(0, height - (framed ? 2 : 0));
25
27
  // The pane is a fixed-height box, so its content is laid out as a flat list
26
28
  // of lines and cut to fit. Letting several adaptive paragraphs share a fixed
27
29
  // box lets them overlap once their natural height exceeds it.
@@ -55,6 +57,6 @@ export function SessionPreview({ session, entries, loading, width, height, home,
55
57
  }
56
58
  // Keep the newest content: an overlong preview is cut at the TOP, so the
57
59
  // last thing said is always the last thing visible.
58
- const visible = lines.length > height ? lines.slice(lines.length - height) : lines;
59
- return (_jsx(Box, { flexDirection: "column", width: width, height: height, flexShrink: 0, paddingLeft: 2, children: visible }));
60
+ const visible = lines.length > bodyHeight ? lines.slice(lines.length - bodyHeight) : lines;
61
+ return (_jsx(Box, { flexDirection: "column", width: width, height: height, flexShrink: 0, borderStyle: framed ? 'round' : undefined, borderColor: framed ? 'permission' : undefined, borderDimColor: true, paddingX: framed ? 1 : 0, paddingLeft: framed ? 1 : 2, overflow: "hidden", children: visible }));
60
62
  }
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import type { ClickEvent } from '../../ink/events/click-event.js';
3
+ import type { WorkspaceGroup } from '../../sessions/view.js';
4
+ /** One two-line working-directory choice in the /resume browser. */
5
+ export declare function WorkspaceListRow({ workspace, all, totalProjects, totalSessions, width, focused, selected, home, now, onClick, }: {
6
+ workspace?: WorkspaceGroup;
7
+ all?: boolean;
8
+ totalProjects: number;
9
+ totalSessions: number;
10
+ width: number;
11
+ focused: boolean;
12
+ selected: boolean;
13
+ home: string;
14
+ now: number;
15
+ onClick?(event: ClickEvent): void;
16
+ }): React.ReactNode;
17
+ //# sourceMappingURL=WorkspaceListRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceListRow.d.ts","sourceRoot":"","sources":["../../../../src/components/sessions/WorkspaceListRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAEjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE5D,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,GAAG,EACH,aAAa,EACb,aAAa,EACb,KAAK,EACL,OAAO,EACP,QAAQ,EACR,IAAI,EACJ,GAAG,EACH,OAAO,GACR,EAAE;IACD,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CAClC,GAAG,KAAK,CAAC,SAAS,CAwClB"}
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text } from '../../ui.js';
4
+ import { t } from '../../i18n.js';
5
+ import { formatProject, formatWhen, projectName, spreadRow, truncateWidth } from '../../sessions/format.js';
6
+ /** One two-line working-directory choice in the /resume browser. */
7
+ export function WorkspaceListRow({ workspace, all, totalProjects, totalSessions, width, focused, selected, home, now, onClick, }) {
8
+ const [hovered, setHovered] = useState(false);
9
+ const body = Math.max(8, width - 4);
10
+ const count = all ? totalSessions : workspace?.count ?? 0;
11
+ const title = all ? t('session-workspace-all') : projectName(workspace?.cwd ?? '');
12
+ const badge = all
13
+ ? t('session-workspace-project-count', { n: totalProjects })
14
+ : workspace?.current
15
+ ? `${t('session-workspace-current')} · ${t('session-count-shown', { n: count })}`
16
+ : t('session-count-shown', { n: count });
17
+ const heading = spreadRow(`${all ? '◎' : '▣'} ${title}`, `${selected ? '✓ ' : ''}${badge}`, body);
18
+ const detail = all
19
+ ? t('session-workspace-all-detail', { n: count })
20
+ : [
21
+ formatProject(workspace?.cwd ?? '', home),
22
+ count > 0 && workspace !== undefined
23
+ ? formatWhen(workspace.updatedAt, now)
24
+ : t('session-workspace-empty'),
25
+ ].filter(Boolean).join(' · ');
26
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, onClick: onClick, onMouseEnter: onClick === undefined ? undefined : () => setHovered(true), onMouseLeave: onClick === undefined ? undefined : () => setHovered(false), backgroundColor: focused || hovered ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Text, { color: focused ? 'suggestion' : 'subtle', children: focused ? '❯ ' : ' ' }), _jsx(Text, { color: focused ? 'suggestion' : selected ? 'success' : 'text', bold: focused || selected, children: heading.left }), _jsx(Text, { dimColor: !focused, children: `${' '.repeat(heading.gap)}${heading.right}` })] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: ` ${truncateWidth(detail, body - 2)}` })] }));
27
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Smooth streaming reveal — decouples "text the provider has delivered" from
3
+ * "text the screen is showing" (the oh-my-pi `display.smoothStreaming` idea,
4
+ * see their `StreamingRevealController`). A shared ~30fps scheduler advances
5
+ * each reveal cursor by an adaptive catch-up step, so burst-delivered text (a
6
+ * slow provider dumping a large batch, or a non-streaming response arriving
7
+ * whole) paints as a brief even flow instead of one jarring jump, while
8
+ * genuinely streaming text is re-paced to a steady cadence regardless of
9
+ * chunk timing.
10
+ *
11
+ * Step math (per frame): `max(MIN_STEP, ceil(backlog / CATCHUP_FRAMES))` —
12
+ * an EXPONENTIAL decay over the backlog (each frame removes ~1/8 of what is
13
+ * still unrevealed, the oh-my-pi semantics exactly): a large burst lands its
14
+ * first three quarters within a few frames, the remainder tapers, and the
15
+ * MIN_STEP floor turns the tail into a steady linear crawl so the reveal
16
+ * always finishes. A ~2000-character one-shot delivery paints fully in
17
+ * roughly 1.5s; genuinely streaming text (small backlogs) paces at a steady
18
+ * per-frame rate. The reveal never runs ahead of what has arrived.
19
+ *
20
+ * ARCHITECTURE — module-level cursors, not component state. dsh-tui's
21
+ * transcript virtualization feeds on MemoRow prop changes (flattened
22
+ * primitives) and re-measures mounted rows after every MessageList commit;
23
+ * a reveal living in child-component state would change row heights without
24
+ * touching those props, and the memo/signature/height-measure chain would
25
+ * never notice (stale cached heights → blank bands). Instead the cursors
26
+ * live here keyed by row identity, the READS happen during render (they feed
27
+ * the same flattened `text` prop the streaming path already uses), and the
28
+ * WRITES happen on the scheduler tick, which bumps a version that
29
+ * MessageList subscribes to via useSyncExternalStore. A reveal therefore
30
+ * rides the exact same update pipeline as an arriving chunk: prop change →
31
+ * memo miss → re-render → post-commit measurement. Render-phase reads are
32
+ * idempotent and safe to re-run (a discarded concurrent render at worst
33
+ * creates a cursor the tick soon retires).
34
+ *
35
+ * Unit model: reveal positions are UTF-16 code units (Chinese text is one
36
+ * unit per character). Surrogate pairs are never split (see {@link
37
+ * safeSliceEnd}); grapheme clusters longer than a surrogate pair (emoji ZWJ
38
+ * chains) may briefly show a partial cluster mid-reveal — transient frames
39
+ * repaint within 33ms, and settled rendering is always the full text.
40
+ */
41
+ /** Reveal tick cadence — ~30fps, matching oh-my-pi's controller. */
42
+ export declare const REVEAL_FRAME_MS: number;
43
+ /** Minimum units (code units or lines) advanced per tick while work remains. */
44
+ export declare const REVEAL_MIN_STEP = 3;
45
+ /** Catch-up bound: the reveal settles any backlog within this many frames. */
46
+ export declare const REVEAL_CATCHUP_FRAMES = 8;
47
+ /** Adaptive per-frame step for a backlog (exported for tests). */
48
+ export declare function revealStep(backlog: number): number;
49
+ /** Subscribe to reveal-frame wakeups (MessageList + tool cards). */
50
+ export declare function subscribeReveal(listener: () => void): () => void;
51
+ /** Snapshot for useSyncExternalStore; bumps once per advancing tick. */
52
+ export declare function getRevealVersion(): number;
53
+ /** Force-complete (and forget) a cursor: expanded card, replaced view, … */
54
+ export declare function snapReveal(key: string): void;
55
+ export type RevealReadOptions = {
56
+ /** Master switch (settings `dsh-tui.smoothStreaming`); false = full text. */
57
+ enabled: boolean;
58
+ /**
59
+ * Whether this content arrived "just now" (live streaming row, freshly
60
+ * settled row, live-created tool card). The FIRST read with active=true
61
+ * creates the cursor at zero — that is what turns a one-shot
62
+ * non-streaming delivery into a smooth paint. Reading with active=false
63
+ * (history / replayed content) never creates one: replaying a transcript
64
+ * must not typewrite every row. `active` only matters for cursor creation;
65
+ * settling mid-reveal deliberately keeps revealing (the "non-streaming
66
+ * becomes streaming" contract).
67
+ */
68
+ active: boolean;
69
+ };
70
+ /**
71
+ * Revealed length of `text` for `key` — the cursor logic of
72
+ * {@link revealTextOf} without the slice allocation (layout signatures read
73
+ * this every render for every visible row).
74
+ */
75
+ export declare function revealLengthOf(key: string, text: string, options: RevealReadOptions): number;
76
+ /**
77
+ * Revealed prefix of `text` for `key`. Monotonic appends (streaming deltas)
78
+ * keep the cursor; a non-prefix replacement (rewind, folded preview
79
+ * truncation) snaps to the new text — never animate across a discontinuity
80
+ * the reader did not witness as streaming. Feed the result into the
81
+ * streaming renderer (StreamingMarkdown); the monotonic prefix it receives
82
+ * keeps its stable-prefix memoization fully effective.
83
+ */
84
+ export declare function revealTextOf(key: string, text: string, options: RevealReadOptions): string;
85
+ /**
86
+ * Revealed unit count for `key` over `total` units (tool-card body lines,
87
+ * split-diff rows). A SMALLER total always snaps (content that shrank is a
88
+ * replacement, not an append); growth keeps the cursor. Callers own the
89
+ * append-vs-replace decision for growing totals (pass a fresh key or call
90
+ * {@link snapReveal} when the content behind the same key is replaced).
91
+ */
92
+ export declare function revealLinesOf(key: string, total: number, options: RevealReadOptions): number;
93
+ /** Clear every cursor/listener/timer (verify scripts between cases). */
94
+ export declare function resetRevealForTest(): void;
95
+ /** Whether the shared scheduler currently runs (refcount assertions). */
96
+ export declare function isRevealTimerRunning(): boolean;
97
+ //# sourceMappingURL=smoothReveal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoothReveal.d.ts","sourceRoot":"","sources":["../../../src/components/smoothReveal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,oEAAoE;AACpE,eAAO,MAAM,eAAe,QAAY,CAAA;AACxC,gFAAgF;AAChF,eAAO,MAAM,eAAe,IAAI,CAAA;AAChC,8EAA8E;AAC9E,eAAO,MAAM,qBAAqB,IAAI,CAAA;AAEtC,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAwGD,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAKhE;AAED,wEAAwE;AACxE,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE5C;AAMD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAA;IAChB;;;;;;;;;OASG;IACH,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAiC5F;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAI1F;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAqC5F;AAMD,wEAAwE;AACxE,wBAAgB,kBAAkB,IAAI,IAAI,CAUzC;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C"}