@stigmer/react 3.0.9-dev.20260616060535 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/execution/ApprovalCard.d.ts +47 -3
  2. package/execution/ApprovalCard.d.ts.map +1 -1
  3. package/execution/ApprovalCard.js +138 -70
  4. package/execution/ApprovalCard.js.map +1 -1
  5. package/execution/ApprovalContext.d.ts +72 -0
  6. package/execution/ApprovalContext.d.ts.map +1 -0
  7. package/execution/ApprovalContext.js +41 -0
  8. package/execution/ApprovalContext.js.map +1 -0
  9. package/execution/ArtifactCard.d.ts.map +1 -1
  10. package/execution/ArtifactCard.js +3 -1
  11. package/execution/ArtifactCard.js.map +1 -1
  12. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  13. package/execution/ArtifactPreviewModal.js +5 -3
  14. package/execution/ArtifactPreviewModal.js.map +1 -1
  15. package/execution/EmptyChangeNotice.d.ts +38 -0
  16. package/execution/EmptyChangeNotice.d.ts.map +1 -0
  17. package/execution/EmptyChangeNotice.js +27 -0
  18. package/execution/EmptyChangeNotice.js.map +1 -0
  19. package/execution/FileChangesView.d.ts +81 -0
  20. package/execution/FileChangesView.d.ts.map +1 -0
  21. package/execution/FileChangesView.js +164 -0
  22. package/execution/FileChangesView.js.map +1 -0
  23. package/execution/FilePathLink.d.ts +24 -4
  24. package/execution/FilePathLink.d.ts.map +1 -1
  25. package/execution/FilePathLink.js +31 -9
  26. package/execution/FilePathLink.js.map +1 -1
  27. package/execution/FileReviewCard.d.ts +111 -0
  28. package/execution/FileReviewCard.d.ts.map +1 -0
  29. package/execution/FileReviewCard.js +558 -0
  30. package/execution/FileReviewCard.js.map +1 -0
  31. package/execution/FileReviewContext.d.ts +35 -0
  32. package/execution/FileReviewContext.d.ts.map +1 -0
  33. package/execution/FileReviewContext.js +23 -0
  34. package/execution/FileReviewContext.js.map +1 -0
  35. package/execution/FileReviewDock.d.ts +73 -0
  36. package/execution/FileReviewDock.d.ts.map +1 -0
  37. package/execution/FileReviewDock.js +65 -0
  38. package/execution/FileReviewDock.js.map +1 -0
  39. package/execution/McpToolDetail.d.ts.map +1 -1
  40. package/execution/McpToolDetail.js +38 -5
  41. package/execution/McpToolDetail.js.map +1 -1
  42. package/execution/MessageThread.d.ts +42 -3
  43. package/execution/MessageThread.d.ts.map +1 -1
  44. package/execution/MessageThread.js +263 -18
  45. package/execution/MessageThread.js.map +1 -1
  46. package/execution/PlanArtifactCard.d.ts.map +1 -1
  47. package/execution/PlanArtifactCard.js +3 -1
  48. package/execution/PlanArtifactCard.js.map +1 -1
  49. package/execution/PlanCompletionCard.d.ts +1 -1
  50. package/execution/PlanCompletionCard.js +1 -1
  51. package/execution/ResultView.d.ts +19 -1
  52. package/execution/ResultView.d.ts.map +1 -1
  53. package/execution/ResultView.js +141 -40
  54. package/execution/ResultView.js.map +1 -1
  55. package/execution/SubAgentSection.d.ts.map +1 -1
  56. package/execution/SubAgentSection.js +45 -6
  57. package/execution/SubAgentSection.js.map +1 -1
  58. package/execution/TerminalSession.d.ts +34 -0
  59. package/execution/TerminalSession.d.ts.map +1 -0
  60. package/execution/TerminalSession.js +34 -0
  61. package/execution/TerminalSession.js.map +1 -0
  62. package/execution/TodoCard.d.ts +53 -0
  63. package/execution/TodoCard.d.ts.map +1 -0
  64. package/execution/TodoCard.js +77 -0
  65. package/execution/TodoCard.js.map +1 -0
  66. package/execution/ToolArgsView.d.ts +7 -1
  67. package/execution/ToolArgsView.d.ts.map +1 -1
  68. package/execution/ToolArgsView.js +10 -9
  69. package/execution/ToolArgsView.js.map +1 -1
  70. package/execution/ToolCallDetail.d.ts +10 -1
  71. package/execution/ToolCallDetail.d.ts.map +1 -1
  72. package/execution/ToolCallDetail.js +66 -19
  73. package/execution/ToolCallDetail.js.map +1 -1
  74. package/execution/ToolCallGroup.d.ts +28 -18
  75. package/execution/ToolCallGroup.d.ts.map +1 -1
  76. package/execution/ToolCallGroup.js +39 -148
  77. package/execution/ToolCallGroup.js.map +1 -1
  78. package/execution/ToolCallItem.d.ts +36 -8
  79. package/execution/ToolCallItem.d.ts.map +1 -1
  80. package/execution/ToolCallItem.js +140 -21
  81. package/execution/ToolCallItem.js.map +1 -1
  82. package/execution/ToolRunGroup.d.ts +48 -0
  83. package/execution/ToolRunGroup.d.ts.map +1 -0
  84. package/execution/ToolRunGroup.js +168 -0
  85. package/execution/ToolRunGroup.js.map +1 -0
  86. package/execution/approval-provenance.d.ts +2 -0
  87. package/execution/approval-provenance.d.ts.map +1 -0
  88. package/execution/approval-provenance.js +6 -0
  89. package/execution/approval-provenance.js.map +1 -0
  90. package/execution/artifact-utils.d.ts +0 -10
  91. package/execution/artifact-utils.d.ts.map +1 -1
  92. package/execution/artifact-utils.js +0 -17
  93. package/execution/artifact-utils.js.map +1 -1
  94. package/execution/execution-phases.d.ts +1 -6
  95. package/execution/execution-phases.d.ts.map +1 -1
  96. package/execution/execution-phases.js +4 -14
  97. package/execution/execution-phases.js.map +1 -1
  98. package/execution/file-path-resolver.d.ts +28 -0
  99. package/execution/file-path-resolver.d.ts.map +1 -1
  100. package/execution/file-path-resolver.js +24 -0
  101. package/execution/file-path-resolver.js.map +1 -1
  102. package/execution/file-review-status.d.ts +127 -0
  103. package/execution/file-review-status.d.ts.map +1 -0
  104. package/execution/file-review-status.js +135 -0
  105. package/execution/file-review-status.js.map +1 -0
  106. package/execution/index.d.ts +39 -5
  107. package/execution/index.d.ts.map +1 -1
  108. package/execution/index.js +23 -3
  109. package/execution/index.js.map +1 -1
  110. package/execution/segment-tool-calls.d.ts +46 -0
  111. package/execution/segment-tool-calls.d.ts.map +1 -0
  112. package/execution/segment-tool-calls.js +71 -0
  113. package/execution/segment-tool-calls.js.map +1 -0
  114. package/execution/tool-categories.d.ts +62 -1
  115. package/execution/tool-categories.d.ts.map +1 -1
  116. package/execution/tool-categories.js +96 -2
  117. package/execution/tool-categories.js.map +1 -1
  118. package/execution/tool-presenter.d.ts +38 -2
  119. package/execution/tool-presenter.d.ts.map +1 -1
  120. package/execution/tool-presenter.js +23 -1
  121. package/execution/tool-presenter.js.map +1 -1
  122. package/execution/tool-rendering-primitives.d.ts.map +1 -1
  123. package/execution/tool-rendering-primitives.js +3 -2
  124. package/execution/tool-rendering-primitives.js.map +1 -1
  125. package/execution/useArtifactDownload.d.ts +44 -0
  126. package/execution/useArtifactDownload.d.ts.map +1 -0
  127. package/execution/useArtifactDownload.js +80 -0
  128. package/execution/useArtifactDownload.js.map +1 -0
  129. package/execution/useArtifactDownloadUrl.d.ts +67 -0
  130. package/execution/useArtifactDownloadUrl.d.ts.map +1 -0
  131. package/execution/useArtifactDownloadUrl.js +59 -0
  132. package/execution/useArtifactDownloadUrl.js.map +1 -0
  133. package/execution/useFileChangeContent.d.ts +61 -0
  134. package/execution/useFileChangeContent.d.ts.map +1 -0
  135. package/execution/useFileChangeContent.js +89 -0
  136. package/execution/useFileChangeContent.js.map +1 -0
  137. package/execution/useFileReview.d.ts +89 -0
  138. package/execution/useFileReview.d.ts.map +1 -0
  139. package/execution/useFileReview.js +106 -0
  140. package/execution/useFileReview.js.map +1 -0
  141. package/execution/useSubmitApproval.d.ts +17 -2
  142. package/execution/useSubmitApproval.d.ts.map +1 -1
  143. package/execution/useSubmitApproval.js +27 -23
  144. package/execution/useSubmitApproval.js.map +1 -1
  145. package/execution/useToolOutputContent.d.ts +44 -0
  146. package/execution/useToolOutputContent.d.ts.map +1 -0
  147. package/execution/useToolOutputContent.js +31 -0
  148. package/execution/useToolOutputContent.js.map +1 -0
  149. package/index.d.ts +6 -6
  150. package/index.d.ts.map +1 -1
  151. package/index.js +3 -3
  152. package/index.js.map +1 -1
  153. package/internal/ApprovalPeekBar.d.ts +23 -0
  154. package/internal/ApprovalPeekBar.d.ts.map +1 -0
  155. package/internal/ApprovalPeekBar.js +25 -0
  156. package/internal/ApprovalPeekBar.js.map +1 -0
  157. package/internal/BoundedContent.d.ts +49 -0
  158. package/internal/BoundedContent.d.ts.map +1 -0
  159. package/internal/BoundedContent.js +50 -0
  160. package/internal/BoundedContent.js.map +1 -0
  161. package/internal/DecisionButton.d.ts +47 -0
  162. package/internal/DecisionButton.d.ts.map +1 -0
  163. package/internal/DecisionButton.js +42 -0
  164. package/internal/DecisionButton.js.map +1 -0
  165. package/internal/InCardDecisionError.d.ts +31 -0
  166. package/internal/InCardDecisionError.d.ts.map +1 -0
  167. package/internal/InCardDecisionError.js +23 -0
  168. package/internal/InCardDecisionError.js.map +1 -0
  169. package/internal/RevealToggle.d.ts +36 -0
  170. package/internal/RevealToggle.d.ts.map +1 -0
  171. package/internal/RevealToggle.js +32 -0
  172. package/internal/RevealToggle.js.map +1 -0
  173. package/internal/VirtualizedThread.d.ts +7 -1
  174. package/internal/VirtualizedThread.d.ts.map +1 -1
  175. package/internal/VirtualizedThread.js +7 -3
  176. package/internal/VirtualizedThread.js.map +1 -1
  177. package/internal/useAutoDisclosure.d.ts +34 -0
  178. package/internal/useAutoDisclosure.d.ts.map +1 -0
  179. package/internal/useAutoDisclosure.js +53 -0
  180. package/internal/useAutoDisclosure.js.map +1 -0
  181. package/internal/useElapsedSince.d.ts +30 -0
  182. package/internal/useElapsedSince.d.ts.map +1 -0
  183. package/internal/useElapsedSince.js +70 -0
  184. package/internal/useElapsedSince.js.map +1 -0
  185. package/internal/useIsOverflowing.d.ts +30 -0
  186. package/internal/useIsOverflowing.d.ts.map +1 -0
  187. package/internal/useIsOverflowing.js +47 -0
  188. package/internal/useIsOverflowing.js.map +1 -0
  189. package/internal/useIsTextTruncated.d.ts +24 -0
  190. package/internal/useIsTextTruncated.d.ts.map +1 -0
  191. package/internal/useIsTextTruncated.js +41 -0
  192. package/internal/useIsTextTruncated.js.map +1 -0
  193. package/internal/useKeyedSubmission.d.ts +49 -0
  194. package/internal/useKeyedSubmission.d.ts.map +1 -0
  195. package/internal/useKeyedSubmission.js +67 -0
  196. package/internal/useKeyedSubmission.js.map +1 -0
  197. package/package.json +4 -4
  198. package/session/SessionViewer.d.ts +2 -2
  199. package/session/SessionViewer.d.ts.map +1 -1
  200. package/session/SessionViewer.js +10 -4
  201. package/session/SessionViewer.js.map +1 -1
  202. package/session/index.d.ts +2 -0
  203. package/session/index.d.ts.map +1 -1
  204. package/session/index.js +1 -0
  205. package/session/index.js.map +1 -1
  206. package/session/inspector/ChangesTab.d.ts +8 -5
  207. package/session/inspector/ChangesTab.d.ts.map +1 -1
  208. package/session/inspector/ChangesTab.js +11 -8
  209. package/session/inspector/ChangesTab.js.map +1 -1
  210. package/session/inspector/SessionInspector.d.ts +4 -4
  211. package/session/inspector/SessionInspector.d.ts.map +1 -1
  212. package/session/inspector/SessionInspector.js +4 -4
  213. package/session/inspector/SessionInspector.js.map +1 -1
  214. package/session/inspector/index.d.ts +0 -2
  215. package/session/inspector/index.d.ts.map +1 -1
  216. package/session/inspector/index.js +0 -1
  217. package/session/inspector/index.js.map +1 -1
  218. package/session/inspector/useSessionInspector.d.ts +8 -4
  219. package/session/inspector/useSessionInspector.d.ts.map +1 -1
  220. package/session/inspector/useSessionInspector.js +27 -20
  221. package/session/inspector/useSessionInspector.js.map +1 -1
  222. package/session/useSessionConversation.d.ts +37 -3
  223. package/session/useSessionConversation.d.ts.map +1 -1
  224. package/session/useSessionConversation.js +18 -2
  225. package/session/useSessionConversation.js.map +1 -1
  226. package/session/useSessionFileChanges.d.ts +74 -0
  227. package/session/useSessionFileChanges.d.ts.map +1 -0
  228. package/session/useSessionFileChanges.js +178 -0
  229. package/session/useSessionFileChanges.js.map +1 -0
  230. package/src/__tests__/styles-border-layer-invariant.test.ts +105 -0
  231. package/src/execution/ApprovalCard.tsx +368 -230
  232. package/src/execution/ApprovalContext.ts +109 -0
  233. package/src/execution/ArtifactCard.tsx +10 -6
  234. package/src/execution/ArtifactPreviewModal.tsx +12 -6
  235. package/src/execution/EmptyChangeNotice.tsx +62 -0
  236. package/src/execution/FileChangesView.tsx +383 -0
  237. package/src/execution/FilePathLink.tsx +65 -12
  238. package/src/execution/FileReviewCard.tsx +1118 -0
  239. package/src/execution/FileReviewContext.ts +53 -0
  240. package/src/execution/FileReviewDock.tsx +157 -0
  241. package/src/execution/McpToolDetail.tsx +98 -32
  242. package/src/execution/MessageThread.tsx +374 -9
  243. package/src/execution/PlanArtifactCard.tsx +9 -6
  244. package/src/execution/PlanCompletionCard.tsx +1 -1
  245. package/src/execution/ResultView.tsx +333 -162
  246. package/src/execution/SubAgentSection.tsx +58 -11
  247. package/src/execution/TerminalSession.tsx +91 -0
  248. package/src/execution/TodoCard.tsx +186 -0
  249. package/src/execution/ToolArgsView.tsx +28 -17
  250. package/src/execution/ToolCallDetail.tsx +137 -26
  251. package/src/execution/ToolCallGroup.tsx +68 -296
  252. package/src/execution/ToolCallItem.tsx +265 -67
  253. package/src/execution/ToolRunGroup.tsx +302 -0
  254. package/src/execution/__tests__/ApprovalCard.test.tsx +395 -5
  255. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +0 -1
  256. package/src/execution/__tests__/EmptyChangeNotice.test.tsx +24 -0
  257. package/src/execution/__tests__/FileChangesView.test.tsx +296 -0
  258. package/src/execution/__tests__/FilePathLink.test.tsx +92 -0
  259. package/src/execution/__tests__/FileReviewCard.test.tsx +1044 -0
  260. package/src/execution/__tests__/FileReviewDock.test.tsx +134 -0
  261. package/src/execution/__tests__/McpToolDetail.test.tsx +79 -0
  262. package/src/execution/__tests__/MessageThread.test.tsx +324 -0
  263. package/src/execution/__tests__/PlanArtifactCard.test.tsx +37 -16
  264. package/src/execution/__tests__/ResultView.test.tsx +280 -2
  265. package/src/execution/__tests__/SubAgentSection.test.tsx +180 -0
  266. package/src/execution/__tests__/TerminalSession.test.tsx +72 -0
  267. package/src/execution/__tests__/TodoCard.test.tsx +102 -0
  268. package/src/execution/__tests__/ToolArgsView.test.tsx +48 -0
  269. package/src/execution/__tests__/ToolCallDetail.test.tsx +195 -0
  270. package/src/execution/__tests__/ToolCallGroup.test.tsx +132 -0
  271. package/src/execution/__tests__/ToolCallItem.test.tsx +521 -0
  272. package/src/execution/__tests__/ToolRunGroup.test.tsx +137 -0
  273. package/src/execution/__tests__/blanked-narration.test.tsx +149 -0
  274. package/src/execution/__tests__/buildThreadItems-approvals.test.ts +304 -0
  275. package/src/execution/__tests__/buildThreadItems-file-review.test.ts +331 -0
  276. package/src/execution/__tests__/file-path-resolver.test.ts +44 -0
  277. package/src/execution/__tests__/file-review-status.test.ts +343 -0
  278. package/src/execution/__tests__/segment-tool-calls.test.ts +124 -0
  279. package/src/execution/__tests__/subagent-file-review-badge.test.tsx +273 -0
  280. package/src/execution/__tests__/thread-keys.test.ts +195 -0
  281. package/src/execution/__tests__/tool-presenter.test.tsx +95 -0
  282. package/src/execution/__tests__/useArtifactDownload.test.tsx +67 -0
  283. package/src/execution/__tests__/useArtifactDownloadUrl.test.tsx +51 -0
  284. package/src/execution/__tests__/useFileChangeContent.test.tsx +240 -0
  285. package/src/execution/__tests__/useFileReview.test.tsx +236 -0
  286. package/src/execution/__tests__/useSubmitApproval.test.tsx +165 -0
  287. package/src/execution/__tests__/useToolOutputContent.test.tsx +82 -0
  288. package/src/execution/approval-provenance.ts +8 -0
  289. package/src/execution/artifact-utils.ts +0 -18
  290. package/src/execution/execution-phases.ts +4 -16
  291. package/src/execution/file-path-resolver.ts +38 -0
  292. package/src/execution/file-review-status.ts +219 -0
  293. package/src/execution/index.ts +84 -4
  294. package/src/execution/segment-tool-calls.ts +101 -0
  295. package/src/execution/tool-categories.ts +128 -1
  296. package/src/execution/tool-presenter.ts +58 -3
  297. package/src/execution/tool-rendering-primitives.tsx +11 -14
  298. package/src/execution/useArtifactDownload.ts +109 -0
  299. package/src/execution/useArtifactDownloadUrl.ts +116 -0
  300. package/src/execution/useFileChangeContent.ts +149 -0
  301. package/src/execution/useFileReview.ts +197 -0
  302. package/src/execution/useSubmitApproval.ts +49 -27
  303. package/src/execution/useToolOutputContent.ts +70 -0
  304. package/src/index.ts +60 -1
  305. package/src/internal/ApprovalPeekBar.tsx +73 -0
  306. package/src/internal/BoundedContent.tsx +97 -0
  307. package/src/internal/DecisionButton.tsx +127 -0
  308. package/src/internal/InCardDecisionError.tsx +47 -0
  309. package/src/internal/RevealToggle.tsx +91 -0
  310. package/src/internal/VirtualizedThread.tsx +28 -2
  311. package/src/internal/__tests__/ApprovalPeekBar.test.tsx +34 -0
  312. package/src/internal/__tests__/BoundedContent.test.tsx +92 -0
  313. package/src/internal/__tests__/DecisionButton.test.tsx +107 -0
  314. package/src/internal/__tests__/RevealToggle.test.tsx +61 -0
  315. package/src/internal/__tests__/useAutoDisclosure.test.ts +74 -0
  316. package/src/internal/__tests__/useElapsedSince.test.tsx +68 -0
  317. package/src/internal/__tests__/useIsOverflowing.test.ts +36 -0
  318. package/src/internal/__tests__/useKeyedSubmission.test.tsx +126 -0
  319. package/src/internal/useAutoDisclosure.ts +58 -0
  320. package/src/internal/useElapsedSince.ts +72 -0
  321. package/src/internal/useIsOverflowing.ts +61 -0
  322. package/src/internal/useIsTextTruncated.ts +55 -0
  323. package/src/internal/useKeyedSubmission.ts +101 -0
  324. package/src/session/SessionViewer.tsx +21 -4
  325. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +198 -0
  326. package/src/session/__tests__/audienceWiring.test.tsx +4 -0
  327. package/src/session/__tests__/useSessionFileChanges.test.ts +303 -0
  328. package/src/session/index.ts +3 -0
  329. package/src/session/inspector/ChangesTab.tsx +20 -17
  330. package/src/session/inspector/SessionInspector.tsx +12 -10
  331. package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +0 -1
  332. package/src/session/inspector/__tests__/ChangesTab.test.tsx +38 -0
  333. package/src/session/inspector/__tests__/useSessionInspector.test.ts +46 -61
  334. package/src/session/inspector/index.ts +0 -3
  335. package/src/session/inspector/useSessionInspector.ts +27 -20
  336. package/src/session/useSessionConversation.ts +80 -3
  337. package/src/session/useSessionFileChanges.ts +207 -0
  338. package/src/styles.css +35 -10
  339. package/src/version-history/DiffViewer.tsx +10 -7
  340. package/src/version-history/UnifiedDiffView.tsx +66 -0
  341. package/src/version-history/__tests__/DiffViewer.test.tsx +57 -0
  342. package/src/version-history/__tests__/UnifiedDiffView.test.tsx +50 -0
  343. package/src/version-history/__tests__/computeDiff.test.ts +35 -0
  344. package/src/version-history/__tests__/parseUnifiedDiff.test.ts +82 -0
  345. package/src/version-history/computeDiff.ts +6 -37
  346. package/src/version-history/diff-hunks.ts +63 -0
  347. package/src/version-history/index.ts +1 -0
  348. package/src/version-history/parseUnifiedDiff.ts +34 -0
  349. package/src/workflow/WorkflowExecutionApprovalCard.tsx +49 -24
  350. package/src/workflow/WorkflowExecutionTimeline.tsx +15 -4
  351. package/src/workflow/WorkflowExecutionTimelineEvent.tsx +8 -3
  352. package/src/workflow/WorkflowExecutionViewer.tsx +72 -28
  353. package/src/workflow/WorkflowFileReviewList.tsx +152 -0
  354. package/src/workflow/WorkflowTaskApprovalCard.tsx +37 -85
  355. package/src/workflow/__tests__/ExecutionInspector.test.tsx +30 -2
  356. package/src/workflow/__tests__/WorkflowExecutionApprovalCard.test.tsx +129 -0
  357. package/src/workflow/__tests__/WorkflowFileReviewList.test.tsx +129 -0
  358. package/src/workflow/__tests__/WorkflowTaskApprovalCard.test.tsx +43 -11
  359. package/src/workflow/__tests__/useWorkflowExecutionActions.test.tsx +276 -0
  360. package/src/workflow/execution-inspector/ExecutionInspector.tsx +42 -18
  361. package/src/workflow/index.ts +5 -0
  362. package/src/workflow/useWorkflowExecutionActions.ts +177 -29
  363. package/styles.css +1 -1
  364. package/version-history/DiffViewer.d.ts.map +1 -1
  365. package/version-history/DiffViewer.js +9 -3
  366. package/version-history/DiffViewer.js.map +1 -1
  367. package/version-history/UnifiedDiffView.d.ts +29 -0
  368. package/version-history/UnifiedDiffView.d.ts.map +1 -0
  369. package/version-history/UnifiedDiffView.js +36 -0
  370. package/version-history/UnifiedDiffView.js.map +1 -0
  371. package/version-history/computeDiff.d.ts +3 -1
  372. package/version-history/computeDiff.d.ts.map +1 -1
  373. package/version-history/computeDiff.js +5 -35
  374. package/version-history/computeDiff.js.map +1 -1
  375. package/version-history/diff-hunks.d.ts +21 -0
  376. package/version-history/diff-hunks.d.ts.map +1 -0
  377. package/version-history/diff-hunks.js +58 -0
  378. package/version-history/diff-hunks.js.map +1 -0
  379. package/version-history/index.d.ts +1 -0
  380. package/version-history/index.d.ts.map +1 -1
  381. package/version-history/index.js +1 -0
  382. package/version-history/index.js.map +1 -1
  383. package/version-history/parseUnifiedDiff.d.ts +22 -0
  384. package/version-history/parseUnifiedDiff.d.ts.map +1 -0
  385. package/version-history/parseUnifiedDiff.js +34 -0
  386. package/version-history/parseUnifiedDiff.js.map +1 -0
  387. package/workflow/WorkflowExecutionApprovalCard.d.ts +8 -1
  388. package/workflow/WorkflowExecutionApprovalCard.d.ts.map +1 -1
  389. package/workflow/WorkflowExecutionApprovalCard.js +17 -3
  390. package/workflow/WorkflowExecutionApprovalCard.js.map +1 -1
  391. package/workflow/WorkflowExecutionTimeline.d.ts +11 -2
  392. package/workflow/WorkflowExecutionTimeline.d.ts.map +1 -1
  393. package/workflow/WorkflowExecutionTimeline.js +2 -2
  394. package/workflow/WorkflowExecutionTimeline.js.map +1 -1
  395. package/workflow/WorkflowExecutionTimelineEvent.d.ts +4 -1
  396. package/workflow/WorkflowExecutionTimelineEvent.d.ts.map +1 -1
  397. package/workflow/WorkflowExecutionTimelineEvent.js +2 -2
  398. package/workflow/WorkflowExecutionTimelineEvent.js.map +1 -1
  399. package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
  400. package/workflow/WorkflowExecutionViewer.js +18 -10
  401. package/workflow/WorkflowExecutionViewer.js.map +1 -1
  402. package/workflow/WorkflowFileReviewList.d.ts +46 -0
  403. package/workflow/WorkflowFileReviewList.d.ts.map +1 -0
  404. package/workflow/WorkflowFileReviewList.js +48 -0
  405. package/workflow/WorkflowFileReviewList.js.map +1 -0
  406. package/workflow/WorkflowTaskApprovalCard.d.ts +8 -1
  407. package/workflow/WorkflowTaskApprovalCard.d.ts.map +1 -1
  408. package/workflow/WorkflowTaskApprovalCard.js +12 -19
  409. package/workflow/WorkflowTaskApprovalCard.js.map +1 -1
  410. package/workflow/execution-inspector/ExecutionInspector.d.ts +21 -4
  411. package/workflow/execution-inspector/ExecutionInspector.d.ts.map +1 -1
  412. package/workflow/execution-inspector/ExecutionInspector.js +6 -3
  413. package/workflow/execution-inspector/ExecutionInspector.js.map +1 -1
  414. package/workflow/index.d.ts +1 -0
  415. package/workflow/index.d.ts.map +1 -1
  416. package/workflow/index.js +1 -0
  417. package/workflow/index.js.map +1 -1
  418. package/workflow/useWorkflowExecutionActions.d.ts +62 -4
  419. package/workflow/useWorkflowExecutionActions.d.ts.map +1 -1
  420. package/workflow/useWorkflowExecutionActions.js +94 -18
  421. package/workflow/useWorkflowExecutionActions.js.map +1 -1
  422. package/session/inspector/PlanTab.d.ts +0 -15
  423. package/session/inspector/PlanTab.d.ts.map +0 -1
  424. package/session/inspector/PlanTab.js +0 -19
  425. package/session/inspector/PlanTab.js.map +0 -1
  426. package/src/session/inspector/PlanTab.tsx +0 -31
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect, afterEach, vi } from "vitest";
2
+ import { render, screen, cleanup, fireEvent } from "@testing-library/react";
3
+ import { RevealToggle } from "../RevealToggle";
4
+
5
+ afterEach(cleanup);
6
+
7
+ describe("RevealToggle", () => {
8
+ it("shows the default 'Show more' label and a collapsed aria-expanded when collapsed", () => {
9
+ render(<RevealToggle expanded={false} onToggle={() => {}} />);
10
+ const btn = screen.getByRole("button", { name: /Show more/ });
11
+ expect(btn.getAttribute("aria-expanded")).toBe("false");
12
+ });
13
+
14
+ it("shows the default 'Show less' label and an expanded aria-expanded when expanded", () => {
15
+ render(<RevealToggle expanded onToggle={() => {}} />);
16
+ const btn = screen.getByRole("button", { name: /Show less/ });
17
+ expect(btn.getAttribute("aria-expanded")).toBe("true");
18
+ });
19
+
20
+ it("honours custom more/less labels (e.g. the line-count primitives)", () => {
21
+ const { rerender } = render(
22
+ <RevealToggle
23
+ expanded={false}
24
+ onToggle={() => {}}
25
+ moreLabel="Show all 42 lines"
26
+ lessLabel="Show less"
27
+ />,
28
+ );
29
+ expect(screen.getByText("Show all 42 lines")).toBeTruthy();
30
+
31
+ rerender(
32
+ <RevealToggle
33
+ expanded
34
+ onToggle={() => {}}
35
+ moreLabel="Show all 42 lines"
36
+ lessLabel="Show less"
37
+ />,
38
+ );
39
+ expect(screen.getByText("Show less")).toBeTruthy();
40
+ });
41
+
42
+ it("invokes onToggle on click", () => {
43
+ const onToggle = vi.fn();
44
+ render(<RevealToggle expanded={false} onToggle={onToggle} />);
45
+ fireEvent.click(screen.getByRole("button"));
46
+ expect(onToggle).toHaveBeenCalledTimes(1);
47
+ });
48
+
49
+ it("forwards the e2e cursor-target hook onto the control", () => {
50
+ render(
51
+ <RevealToggle
52
+ expanded={false}
53
+ onToggle={() => {}}
54
+ cursorTarget="tool-detail-expand"
55
+ />,
56
+ );
57
+ expect(
58
+ screen.getByRole("button").getAttribute("data-cursor-target"),
59
+ ).toBe("tool-detail-expand");
60
+ });
61
+ });
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { act, renderHook } from "@testing-library/react";
3
+ import { useAutoDisclosure } from "../useAutoDisclosure";
4
+
5
+ describe("useAutoDisclosure", () => {
6
+ it("starts open when autoOpen is true", () => {
7
+ const { result } = renderHook(() => useAutoDisclosure(true));
8
+ expect(result.current[0]).toBe(true);
9
+ });
10
+
11
+ it("starts closed when autoOpen is false", () => {
12
+ const { result } = renderHook(() => useAutoDisclosure(false));
13
+ expect(result.current[0]).toBe(false);
14
+ });
15
+
16
+ it("honours initialOpen over autoOpen for the first render", () => {
17
+ const { result } = renderHook(() =>
18
+ useAutoDisclosure(false, { initialOpen: true }),
19
+ );
20
+ expect(result.current[0]).toBe(true);
21
+ });
22
+
23
+ it("tracks autoOpen while the user has not toggled", () => {
24
+ const { result, rerender } = renderHook(
25
+ ({ autoOpen }) => useAutoDisclosure(autoOpen),
26
+ { initialProps: { autoOpen: true } },
27
+ );
28
+ expect(result.current[0]).toBe(true);
29
+
30
+ rerender({ autoOpen: false });
31
+ expect(result.current[0]).toBe(false);
32
+
33
+ rerender({ autoOpen: true });
34
+ expect(result.current[0]).toBe(true);
35
+ });
36
+
37
+ it("freezes on the user's choice once toggled, ignoring autoOpen", () => {
38
+ const { result, rerender } = renderHook(
39
+ ({ autoOpen }) => useAutoDisclosure(autoOpen),
40
+ { initialProps: { autoOpen: true } },
41
+ );
42
+
43
+ // User collapses while the section is auto-open.
44
+ act(() => result.current[1]());
45
+ expect(result.current[0]).toBe(false);
46
+
47
+ // Auto signal flips both ways — user's collapse must persist.
48
+ rerender({ autoOpen: false });
49
+ expect(result.current[0]).toBe(false);
50
+ rerender({ autoOpen: true });
51
+ expect(result.current[0]).toBe(false);
52
+ });
53
+
54
+ it("toggle flips from a user-opened state too", () => {
55
+ const { result } = renderHook(() => useAutoDisclosure(false));
56
+ expect(result.current[0]).toBe(false);
57
+
58
+ act(() => result.current[1]());
59
+ expect(result.current[0]).toBe(true);
60
+
61
+ act(() => result.current[1]());
62
+ expect(result.current[0]).toBe(false);
63
+ });
64
+
65
+ it("keeps a stable toggle identity across renders", () => {
66
+ const { result, rerender } = renderHook(
67
+ ({ autoOpen }) => useAutoDisclosure(autoOpen),
68
+ { initialProps: { autoOpen: false } },
69
+ );
70
+ const first = result.current[1];
71
+ rerender({ autoOpen: true });
72
+ expect(result.current[1]).toBe(first);
73
+ });
74
+ });
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { renderHook, act, cleanup } from "@testing-library/react";
3
+ import { useElapsedSince, formatElapsed } from "../useElapsedSince";
4
+
5
+ beforeEach(() => {
6
+ vi.useFakeTimers();
7
+ vi.setSystemTime(new Date("2026-07-02T12:00:30Z"));
8
+ });
9
+
10
+ afterEach(() => {
11
+ cleanup();
12
+ vi.useRealTimers();
13
+ });
14
+
15
+ describe("useElapsedSince", () => {
16
+ it("returns the elapsed ms since the timestamp", () => {
17
+ const { result } = renderHook(() =>
18
+ useElapsedSince("2026-07-02T12:00:00Z"),
19
+ );
20
+ expect(result.current).toBe(30_000);
21
+ });
22
+
23
+ it("ticks forward once per second", () => {
24
+ const { result } = renderHook(() =>
25
+ useElapsedSince("2026-07-02T12:00:00Z"),
26
+ );
27
+ act(() => {
28
+ vi.advanceTimersByTime(3_000);
29
+ });
30
+ expect(result.current).toBe(33_000);
31
+ });
32
+
33
+ it("clamps to zero when the timestamp is ahead of the client clock", () => {
34
+ const { result } = renderHook(() =>
35
+ useElapsedSince("2026-07-02T12:00:35Z"),
36
+ );
37
+ expect(result.current).toBe(0);
38
+ });
39
+
40
+ it("returns null for an empty timestamp", () => {
41
+ const { result } = renderHook(() => useElapsedSince(""));
42
+ expect(result.current).toBeNull();
43
+ });
44
+
45
+ it("returns null for an unparseable timestamp", () => {
46
+ const { result } = renderHook(() => useElapsedSince("not-a-date"));
47
+ expect(result.current).toBeNull();
48
+ });
49
+
50
+ it("tears down its interval on unmount", () => {
51
+ const { unmount } = renderHook(() =>
52
+ useElapsedSince("2026-07-02T12:00:00Z"),
53
+ );
54
+ unmount();
55
+ expect(vi.getTimerCount()).toBe(0);
56
+ });
57
+ });
58
+
59
+ describe("formatElapsed", () => {
60
+ it("formats each magnitude", () => {
61
+ expect(formatElapsed(500)).toBe("just now");
62
+ expect(formatElapsed(42_000)).toBe("42s");
63
+ expect(formatElapsed(3 * 60_000 + 12_000)).toBe("3m 12s");
64
+ expect(formatElapsed(5 * 60_000)).toBe("5m");
65
+ expect(formatElapsed(65 * 60_000)).toBe("1h 5m");
66
+ expect(formatElapsed(120 * 60_000)).toBe("2h");
67
+ });
68
+ });
@@ -0,0 +1,36 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { createElement } from "react";
3
+ import { render, cleanup } from "@testing-library/react";
4
+ import { useIsOverflowing } from "../useIsOverflowing";
5
+
6
+ afterEach(cleanup);
7
+
8
+ function Probe({
9
+ enabled,
10
+ onState,
11
+ }: {
12
+ enabled: boolean;
13
+ onState: (overflowing: boolean) => void;
14
+ }) {
15
+ const { ref, isOverflowing } = useIsOverflowing<HTMLDivElement>(enabled);
16
+ onState(isOverflowing);
17
+ return createElement("div", { ref }, "content");
18
+ }
19
+
20
+ // happy-dom does not compute layout (scrollHeight === clientHeight === 0), so the
21
+ // deterministic contract here is the `enabled` gate and the not-overflowing
22
+ // baseline; the clipped (overflowing) case is verified against a real browser in
23
+ // the e2e layer, mirroring the SDK's documented unit/e2e split.
24
+ describe("useIsOverflowing", () => {
25
+ it("reports not-overflowing when disabled (observer never attached)", () => {
26
+ let state = true;
27
+ render(createElement(Probe, { enabled: false, onState: (s) => (state = s) }));
28
+ expect(state).toBe(false);
29
+ });
30
+
31
+ it("reports not-overflowing for content that fits its container", () => {
32
+ let state = true;
33
+ render(createElement(Probe, { enabled: true, onState: (s) => (state = s) }));
34
+ expect(state).toBe(false);
35
+ });
36
+ });
@@ -0,0 +1,126 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { renderHook, act } from "@testing-library/react";
3
+ import { useKeyedSubmission } from "../useKeyedSubmission";
4
+
5
+ describe("useKeyedSubmission", () => {
6
+ it("tracks a key while its operation is in flight, then releases it", async () => {
7
+ let resolve!: () => void;
8
+ const { result } = renderHook(() => useKeyedSubmission<void>());
9
+
10
+ let pending: Promise<void>;
11
+ act(() => {
12
+ pending = result.current.run("k-1", () => new Promise<void>((r) => (resolve = r)));
13
+ });
14
+
15
+ expect(result.current.submittingKeys.has("k-1")).toBe(true);
16
+
17
+ await act(async () => {
18
+ resolve();
19
+ await pending;
20
+ });
21
+
22
+ expect(result.current.submittingKeys.size).toBe(0);
23
+ });
24
+
25
+ it("records a failure keyed, releases the in-flight key, then rethrows", async () => {
26
+ const { result } = renderHook(() => useKeyedSubmission<void>());
27
+
28
+ await act(async () => {
29
+ await expect(
30
+ result.current.run("k-1", () => Promise.reject(new Error("boom"))),
31
+ ).rejects.toThrow("boom");
32
+ });
33
+
34
+ expect(result.current.errorsByKey.get("k-1")?.message).toBe("boom");
35
+ expect(result.current.submittingKeys.size).toBe(0);
36
+ });
37
+
38
+ it("coerces a non-Error rejection into an Error", async () => {
39
+ const { result } = renderHook(() => useKeyedSubmission<void>());
40
+
41
+ await act(async () => {
42
+ await expect(
43
+ result.current.run("k-1", () => Promise.reject("plain string")),
44
+ ).rejects.toBe("plain string");
45
+ });
46
+
47
+ const recorded = result.current.errorsByKey.get("k-1");
48
+ expect(recorded).toBeInstanceOf(Error);
49
+ expect(recorded?.message).toBe("plain string");
50
+ });
51
+
52
+ it("keys two failures independently — one never clobbers another", async () => {
53
+ const { result } = renderHook(() => useKeyedSubmission<void>());
54
+
55
+ await act(async () => {
56
+ await expect(
57
+ result.current.run("k-a", () => Promise.reject(new Error("fail-a"))),
58
+ ).rejects.toThrow("fail-a");
59
+ });
60
+ await act(async () => {
61
+ await expect(
62
+ result.current.run("k-b", () => Promise.reject(new Error("fail-b"))),
63
+ ).rejects.toThrow("fail-b");
64
+ });
65
+
66
+ expect(result.current.errorsByKey.get("k-a")?.message).toBe("fail-a");
67
+ expect(result.current.errorsByKey.get("k-b")?.message).toBe("fail-b");
68
+ });
69
+
70
+ it("clears a key's prior error at retry-start, leaving siblings untouched", async () => {
71
+ const { result } = renderHook(() => useKeyedSubmission<void>());
72
+
73
+ await act(async () => {
74
+ await expect(
75
+ result.current.run("k-a", () => Promise.reject(new Error("fail-a"))),
76
+ ).rejects.toThrow("fail-a");
77
+ });
78
+ await act(async () => {
79
+ await expect(
80
+ result.current.run("k-b", () => Promise.reject(new Error("fail-b"))),
81
+ ).rejects.toThrow("fail-b");
82
+ });
83
+
84
+ // Retrying k-a succeeds: its error clears at run-start; k-b's persists.
85
+ await act(async () => {
86
+ await result.current.run("k-a", () => Promise.resolve());
87
+ });
88
+
89
+ expect(result.current.errorsByKey.has("k-a")).toBe(false);
90
+ expect(result.current.errorsByKey.get("k-b")?.message).toBe("fail-b");
91
+ });
92
+
93
+ it("returns the resolved value on success", async () => {
94
+ const { result } = renderHook(() => useKeyedSubmission<number>());
95
+
96
+ let value: number | undefined;
97
+ await act(async () => {
98
+ value = await result.current.run("k-1", () => Promise.resolve(42));
99
+ });
100
+
101
+ expect(value).toBe(42);
102
+ expect(result.current.errorsByKey.size).toBe(0);
103
+ });
104
+
105
+ it("clearErrors resets every recorded error", async () => {
106
+ const { result } = renderHook(() => useKeyedSubmission<void>());
107
+
108
+ await act(async () => {
109
+ await expect(
110
+ result.current.run("k-a", () => Promise.reject(new Error("fail-a"))),
111
+ ).rejects.toThrow("fail-a");
112
+ });
113
+ expect(result.current.errorsByKey.size).toBe(1);
114
+
115
+ act(() => result.current.clearErrors());
116
+ expect(result.current.errorsByKey.size).toBe(0);
117
+ });
118
+
119
+ it("keeps a stable return reference across renders when nothing changes", () => {
120
+ const { result, rerender } = renderHook(() => useKeyedSubmission<void>());
121
+
122
+ const first = result.current;
123
+ rerender();
124
+ expect(result.current).toBe(first);
125
+ });
126
+ });
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
+ /**
6
+ * Disclosure state machine for progressively-revealed thread sections
7
+ * (tool-call groups, individual tool rows, sub-agent cards).
8
+ *
9
+ * The challenge it solves: a section should open and close *on its own* as
10
+ * the agent works (open while a tool runs, settle closed when it finishes),
11
+ * yet a user's explicit click must win and never be stomped by the next
12
+ * stream frame. Three components hand-rolled this same "auto unless the user
13
+ * intervened" logic; this hook is the single source of that shape.
14
+ *
15
+ * `autoOpen` is the component's live opinion of whether the section *should*
16
+ * be open right now (e.g. `isRunning || hasPendingApproval`). While the user
17
+ * has not toggled, `open` tracks `autoOpen`. Once the user toggles, their
18
+ * choice sticks for the lifetime of the component — the auto signal is
19
+ * ignored from then on, so the section never reopens or recollapses under
20
+ * them mid-stream.
21
+ *
22
+ * @param autoOpen Whether the section should auto-open given current state.
23
+ * @param options.initialOpen First-render open state. Defaults to `autoOpen`.
24
+ * Use it to honour a caller-provided `defaultExpanded` before the first
25
+ * effect runs.
26
+ * @returns A `[open, toggle]` tuple. `toggle` flips the state and marks it
27
+ * user-controlled.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * const [open, toggle] = useAutoDisclosure(isRunning || hasPendingApproval);
32
+ * <button aria-expanded={open} onClick={toggle}>…</button>
33
+ * ```
34
+ */
35
+ export function useAutoDisclosure(
36
+ autoOpen: boolean,
37
+ options?: { readonly initialOpen?: boolean },
38
+ ): readonly [open: boolean, toggle: () => void] {
39
+ const [open, setOpen] = useState(options?.initialOpen ?? autoOpen);
40
+ const userToggledRef = useRef(false);
41
+ const prevAutoOpenRef = useRef(autoOpen);
42
+
43
+ useEffect(() => {
44
+ // Sync only on a genuine transition of the auto signal — never on mount,
45
+ // so `initialOpen` is honoured, and never against the user's choice.
46
+ if (prevAutoOpenRef.current === autoOpen) return;
47
+ prevAutoOpenRef.current = autoOpen;
48
+ if (userToggledRef.current) return;
49
+ setOpen(autoOpen);
50
+ }, [autoOpen]);
51
+
52
+ const toggle = useRef(() => {
53
+ userToggledRef.current = true;
54
+ setOpen((v) => !v);
55
+ }).current;
56
+
57
+ return [open, toggle];
58
+ }
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useState } from "react";
4
+
5
+ /**
6
+ * Live elapsed time since an ISO 8601 timestamp, re-rendering once per second.
7
+ *
8
+ * The shape behind every "how long has this been going" affordance in the
9
+ * thread: an approval gate waiting on a human, a sub-agent card whose nested
10
+ * activity cannot be streamed (the Cursor SDK returns sub-agent internals only
11
+ * at completion), a long-running tool. A ticking duration is the honest signal
12
+ * that work is progressing when there is nothing else to show — as opposed to
13
+ * fabricating intermediate state.
14
+ *
15
+ * Returns elapsed milliseconds (clamped to >= 0 against client/server clock
16
+ * skew), or `null` when the timestamp is absent or unparseable — callers
17
+ * render nothing rather than a bogus "0s". The interval is scoped to the
18
+ * component instance and torn down on unmount, so a thread full of settled
19
+ * cards costs zero timers.
20
+ *
21
+ * @param startedAt ISO 8601 timestamp the clock counts from (e.g.
22
+ * `ToolCall.approval_requested_at`, `SubAgentExecution.started_at`).
23
+ */
24
+ export function useElapsedSince(startedAt: string): number | null {
25
+ const startMs = useMemo(() => {
26
+ if (!startedAt) return null;
27
+ const t = new Date(startedAt).getTime();
28
+ return Number.isNaN(t) ? null : t;
29
+ }, [startedAt]);
30
+
31
+ const [elapsed, setElapsed] = useState<number | null>(null);
32
+
33
+ useEffect(() => {
34
+ if (startMs === null) {
35
+ setElapsed(null);
36
+ return;
37
+ }
38
+ setElapsed(Math.max(0, Date.now() - startMs));
39
+ const id = setInterval(() => {
40
+ setElapsed(Math.max(0, Date.now() - startMs));
41
+ }, 1000);
42
+ return () => clearInterval(id);
43
+ }, [startMs]);
44
+
45
+ return startMs === null ? null : elapsed;
46
+ }
47
+
48
+ /**
49
+ * Format an elapsed duration for the live tickers driven by
50
+ * {@link useElapsedSince}: `just now`, `42s`, `3m 12s`, `1h 5m`.
51
+ *
52
+ * Second-granularity (unlike ToolCallDetail's `formatDuration`, which keeps
53
+ * millisecond precision for completed calls) because a live counter that
54
+ * flickers sub-second digits reads as noise, not progress.
55
+ */
56
+ export function formatElapsed(ms: number): string {
57
+ if (ms < 1000) return "just now";
58
+ const seconds = Math.floor(ms / 1000);
59
+ if (seconds < 60) return `${seconds}s`;
60
+ const minutes = Math.floor(seconds / 60);
61
+ const remainingSeconds = seconds % 60;
62
+ if (minutes < 60) {
63
+ return remainingSeconds > 0
64
+ ? `${minutes}m ${remainingSeconds}s`
65
+ : `${minutes}m`;
66
+ }
67
+ const hours = Math.floor(minutes / 60);
68
+ const remainingMinutes = minutes % 60;
69
+ return remainingMinutes > 0
70
+ ? `${hours}h ${remainingMinutes}m`
71
+ : `${hours}h`;
72
+ }
@@ -0,0 +1,61 @@
1
+ import { useLayoutEffect, useRef, useState } from "react";
2
+
3
+ export interface UseIsOverflowingReturn<T extends HTMLElement> {
4
+ /** Attach to the element whose vertical overflow should be tracked. */
5
+ readonly ref: React.RefObject<T | null>;
6
+ /** `true` when the element's content is clipped by a vertical clamp (e.g. `overflow-hidden` + `max-h-*`). */
7
+ readonly isOverflowing: boolean;
8
+ }
9
+
10
+ /**
11
+ * Tracks whether a vertically-clamped element (e.g. one styled with
12
+ * `overflow-hidden max-h-*`) is actually clipping its content at the current
13
+ * height.
14
+ *
15
+ * The vertical analog of {@link useIsTextTruncated}: it measures
16
+ * `scrollHeight > clientHeight` synchronously via `useLayoutEffect` (so the
17
+ * first value is correct before paint — no flash) and keeps it in sync with
18
+ * content-size changes via a `ResizeObserver`. The state setter compares before
19
+ * writing, so a stable layout never triggers a re-render loop.
20
+ *
21
+ * Pass `enabled: false` to opt out entirely — the observer is not attached and
22
+ * `isOverflowing` stays `false`. Callers gate on this so only the elements that
23
+ * consume the signal pay for the measurement.
24
+ *
25
+ * Note: under a layout-free test environment (`happy-dom`) `scrollHeight` and
26
+ * `clientHeight` are both `0`, so this reports `false`; overflow-driven UI is
27
+ * therefore verified in the e2e (real-browser) layer, mirroring the split the
28
+ * rest of the SDK already documents.
29
+ *
30
+ * @internal
31
+ */
32
+ export function useIsOverflowing<T extends HTMLElement = HTMLElement>(
33
+ enabled: boolean,
34
+ ): UseIsOverflowingReturn<T> {
35
+ const ref = useRef<T | null>(null);
36
+ const [isOverflowing, setIsOverflowing] = useState(false);
37
+
38
+ useLayoutEffect(() => {
39
+ if (!enabled) {
40
+ // Reset so an element that stops consuming the signal doesn't keep a stale true.
41
+ setIsOverflowing((prev) => (prev ? false : prev));
42
+ return;
43
+ }
44
+
45
+ const el = ref.current;
46
+ if (!el) return;
47
+
48
+ const measure = () => {
49
+ const next = el.scrollHeight > el.clientHeight + 1;
50
+ setIsOverflowing((prev) => (prev === next ? prev : next));
51
+ };
52
+
53
+ measure();
54
+
55
+ const ro = new ResizeObserver(measure);
56
+ ro.observe(el);
57
+ return () => ro.disconnect();
58
+ }, [enabled]);
59
+
60
+ return { ref, isOverflowing };
61
+ }
@@ -0,0 +1,55 @@
1
+ import { useLayoutEffect, useRef, useState } from "react";
2
+
3
+ export interface UseIsTextTruncatedReturn<T extends HTMLElement> {
4
+ /** Attach to the element whose horizontal overflow should be tracked. */
5
+ readonly ref: React.RefObject<T | null>;
6
+ /** `true` when the element's content is clipped by `overflow` (e.g. `truncate`). */
7
+ readonly isTruncated: boolean;
8
+ }
9
+
10
+ /**
11
+ * Tracks whether a single-line, overflow-clipped element (e.g. one styled with
12
+ * Tailwind's `truncate`) is actually truncating its text at the current width.
13
+ *
14
+ * The horizontal analog of {@link useScrollShadows}: it measures
15
+ * `scrollWidth > clientWidth` synchronously via `useLayoutEffect` (so the first
16
+ * value is correct before paint — no flash) and keeps it in sync with width
17
+ * changes via a `ResizeObserver`. The state setter compares before writing, so a
18
+ * stable layout never triggers a re-render loop.
19
+ *
20
+ * Pass `enabled: false` to opt a row out entirely — the observer is not attached
21
+ * and `isTruncated` stays `false`. Callers gate on this so only the rows that
22
+ * consume the signal pay for the measurement.
23
+ *
24
+ * @internal
25
+ */
26
+ export function useIsTextTruncated<T extends HTMLElement = HTMLElement>(
27
+ enabled: boolean,
28
+ ): UseIsTextTruncatedReturn<T> {
29
+ const ref = useRef<T | null>(null);
30
+ const [isTruncated, setIsTruncated] = useState(false);
31
+
32
+ useLayoutEffect(() => {
33
+ if (!enabled) {
34
+ // Reset so a row that stops consuming the signal doesn't keep a stale true.
35
+ setIsTruncated((prev) => (prev ? false : prev));
36
+ return;
37
+ }
38
+
39
+ const el = ref.current;
40
+ if (!el) return;
41
+
42
+ const measure = () => {
43
+ const next = el.scrollWidth > el.clientWidth + 1;
44
+ setIsTruncated((prev) => (prev === next ? prev : next));
45
+ };
46
+
47
+ measure();
48
+
49
+ const ro = new ResizeObserver(measure);
50
+ ro.observe(el);
51
+ return () => ro.disconnect();
52
+ }, [enabled]);
53
+
54
+ return { ref, isTruncated };
55
+ }