@stigmer/react 3.1.1 → 3.1.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 (426) hide show
  1. package/composer/SessionComposer.d.ts +24 -1
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +19 -8
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/execution/ArtifactContentRenderer.d.ts +11 -1
  6. package/execution/ArtifactContentRenderer.d.ts.map +1 -1
  7. package/execution/ArtifactContentRenderer.js +39 -15
  8. package/execution/ArtifactContentRenderer.js.map +1 -1
  9. package/execution/FilePathContext.d.ts +8 -1
  10. package/execution/FilePathContext.d.ts.map +1 -1
  11. package/execution/FilePathContext.js.map +1 -1
  12. package/execution/FilePathLink.d.ts.map +1 -1
  13. package/execution/FilePathLink.js +21 -6
  14. package/execution/FilePathLink.js.map +1 -1
  15. package/execution/MessageEntry.d.ts +29 -0
  16. package/execution/MessageEntry.d.ts.map +1 -1
  17. package/execution/MessageEntry.js +29 -5
  18. package/execution/MessageEntry.js.map +1 -1
  19. package/execution/MessageThread.d.ts +76 -13
  20. package/execution/MessageThread.d.ts.map +1 -1
  21. package/execution/MessageThread.js +178 -24
  22. package/execution/MessageThread.js.map +1 -1
  23. package/execution/PlanArtifactCard.d.ts +27 -18
  24. package/execution/PlanArtifactCard.d.ts.map +1 -1
  25. package/execution/PlanArtifactCard.js +13 -23
  26. package/execution/PlanArtifactCard.js.map +1 -1
  27. package/execution/PlanDocumentMessage.d.ts +32 -0
  28. package/execution/PlanDocumentMessage.d.ts.map +1 -0
  29. package/execution/PlanDocumentMessage.js +37 -0
  30. package/execution/PlanDocumentMessage.js.map +1 -0
  31. package/execution/PlanStreamingCard.d.ts +45 -0
  32. package/execution/PlanStreamingCard.d.ts.map +1 -0
  33. package/execution/PlanStreamingCard.js +35 -0
  34. package/execution/PlanStreamingCard.js.map +1 -0
  35. package/execution/index.d.ts +5 -1
  36. package/execution/index.d.ts.map +1 -1
  37. package/execution/index.js +2 -0
  38. package/execution/index.js.map +1 -1
  39. package/execution/useCreateAgentExecution.d.ts +10 -0
  40. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  41. package/execution/useCreateAgentExecution.js +5 -1
  42. package/execution/useCreateAgentExecution.js.map +1 -1
  43. package/github/decodeGitHubContent.d.ts +29 -0
  44. package/github/decodeGitHubContent.d.ts.map +1 -0
  45. package/github/decodeGitHubContent.js +72 -0
  46. package/github/decodeGitHubContent.js.map +1 -0
  47. package/github/index.d.ts +1 -0
  48. package/github/index.d.ts.map +1 -1
  49. package/github/index.js +1 -0
  50. package/github/index.js.map +1 -1
  51. package/github/useGitHubFileReader.d.ts +26 -0
  52. package/github/useGitHubFileReader.d.ts.map +1 -0
  53. package/github/useGitHubFileReader.js +106 -0
  54. package/github/useGitHubFileReader.js.map +1 -0
  55. package/github/useGitHubTreeLister.d.ts.map +1 -1
  56. package/github/useGitHubTreeLister.js +6 -2
  57. package/github/useGitHubTreeLister.js.map +1 -1
  58. package/index.d.ts +9 -9
  59. package/index.d.ts.map +1 -1
  60. package/index.js +5 -5
  61. package/index.js.map +1 -1
  62. package/internal/ResizableSplit.d.ts +72 -22
  63. package/internal/ResizableSplit.d.ts.map +1 -1
  64. package/internal/ResizableSplit.js +96 -38
  65. package/internal/ResizableSplit.js.map +1 -1
  66. package/internal/VirtualizedThread.d.ts +5 -3
  67. package/internal/VirtualizedThread.d.ts.map +1 -1
  68. package/internal/VirtualizedThread.js +6 -4
  69. package/internal/VirtualizedThread.js.map +1 -1
  70. package/internal/file-icons/index.d.ts +17 -0
  71. package/internal/file-icons/index.d.ts.map +1 -0
  72. package/internal/file-icons/index.js +60 -0
  73. package/internal/file-icons/index.js.map +1 -0
  74. package/internal/file-tree/FileTreeNode.d.ts +18 -1
  75. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  76. package/internal/file-tree/FileTreeNode.js +11 -2
  77. package/internal/file-tree/FileTreeNode.js.map +1 -1
  78. package/internal/markdown-components.d.ts +38 -1
  79. package/internal/markdown-components.d.ts.map +1 -1
  80. package/internal/markdown-components.js +86 -11
  81. package/internal/markdown-components.js.map +1 -1
  82. package/{workflow → internal}/motion-preference.d.ts +7 -4
  83. package/internal/motion-preference.d.ts.map +1 -0
  84. package/{workflow → internal}/motion-preference.js +7 -4
  85. package/internal/motion-preference.js.map +1 -0
  86. package/internal/store/index.d.ts +33 -0
  87. package/internal/store/index.d.ts.map +1 -1
  88. package/internal/store/index.js +59 -0
  89. package/internal/store/index.js.map +1 -1
  90. package/internal/store/workspace-editors-store.d.ts +145 -0
  91. package/internal/store/workspace-editors-store.d.ts.map +1 -0
  92. package/internal/store/workspace-editors-store.js +208 -0
  93. package/internal/store/workspace-editors-store.js.map +1 -0
  94. package/internal/store/workspace-file-selection-store.d.ts +38 -0
  95. package/internal/store/workspace-file-selection-store.d.ts.map +1 -0
  96. package/internal/store/workspace-file-selection-store.js +62 -0
  97. package/internal/store/workspace-file-selection-store.js.map +1 -0
  98. package/internal/useRevealLine.d.ts +49 -0
  99. package/internal/useRevealLine.d.ts.map +1 -0
  100. package/internal/useRevealLine.js +43 -0
  101. package/internal/useRevealLine.js.map +1 -0
  102. package/library/detect-plan-artifact.d.ts +21 -0
  103. package/library/detect-plan-artifact.d.ts.map +1 -1
  104. package/library/detect-plan-artifact.js +20 -0
  105. package/library/detect-plan-artifact.js.map +1 -1
  106. package/library/detect-streaming-plan.d.ts +45 -0
  107. package/library/detect-streaming-plan.d.ts.map +1 -0
  108. package/library/detect-streaming-plan.js +83 -0
  109. package/library/detect-streaming-plan.js.map +1 -0
  110. package/library/index.d.ts +4 -1
  111. package/library/index.d.ts.map +1 -1
  112. package/library/index.js +2 -1
  113. package/library/index.js.map +1 -1
  114. package/package.json +4 -4
  115. package/session/NewSessionViewer.d.ts +19 -4
  116. package/session/NewSessionViewer.d.ts.map +1 -1
  117. package/session/NewSessionViewer.js +52 -25
  118. package/session/NewSessionViewer.js.map +1 -1
  119. package/session/PlanEditor.d.ts +55 -0
  120. package/session/PlanEditor.d.ts.map +1 -0
  121. package/session/PlanEditor.js +88 -0
  122. package/session/PlanEditor.js.map +1 -0
  123. package/session/PlanStreamingDocument.d.ts +30 -0
  124. package/session/PlanStreamingDocument.d.ts.map +1 -0
  125. package/session/PlanStreamingDocument.js +37 -0
  126. package/session/PlanStreamingDocument.js.map +1 -0
  127. package/session/SessionPanelChip.d.ts +26 -0
  128. package/session/SessionPanelChip.d.ts.map +1 -0
  129. package/session/SessionPanelChip.js +40 -0
  130. package/session/SessionPanelChip.js.map +1 -0
  131. package/session/SessionViewer.d.ts +34 -7
  132. package/session/SessionViewer.d.ts.map +1 -1
  133. package/session/SessionViewer.js +315 -33
  134. package/session/SessionViewer.js.map +1 -1
  135. package/session/{inspector → facets}/ArtifactsTab.d.ts +10 -2
  136. package/session/facets/ArtifactsTab.d.ts.map +1 -0
  137. package/session/{inspector → facets}/ArtifactsTab.js +8 -3
  138. package/session/facets/ArtifactsTab.js.map +1 -0
  139. package/session/{inspector → facets}/ChangesTab.d.ts +3 -3
  140. package/session/facets/ChangesTab.d.ts.map +1 -0
  141. package/session/{inspector → facets}/ChangesTab.js +3 -3
  142. package/session/facets/ChangesTab.js.map +1 -0
  143. package/session/facets/InspectTab.d.ts.map +1 -0
  144. package/session/facets/InspectTab.js.map +1 -0
  145. package/session/{inspector → facets}/SetupTab.d.ts +11 -2
  146. package/session/facets/SetupTab.d.ts.map +1 -0
  147. package/session/{inspector → facets}/SetupTab.js +4 -3
  148. package/session/facets/SetupTab.js.map +1 -0
  149. package/session/{inspector → facets}/UsageTab.d.ts +1 -1
  150. package/session/facets/UsageTab.d.ts.map +1 -0
  151. package/session/{inspector → facets}/UsageTab.js +1 -1
  152. package/session/facets/UsageTab.js.map +1 -0
  153. package/session/{inspector → facets}/index.d.ts +0 -6
  154. package/session/facets/index.d.ts.map +1 -0
  155. package/session/{inspector → facets}/index.js +0 -3
  156. package/session/facets/index.js.map +1 -0
  157. package/session/index.d.ts +11 -2
  158. package/session/index.d.ts.map +1 -1
  159. package/session/index.js +15 -2
  160. package/session/index.js.map +1 -1
  161. package/session/plan-document.d.ts +9 -0
  162. package/session/plan-document.d.ts.map +1 -0
  163. package/session/plan-document.js +17 -0
  164. package/session/plan-document.js.map +1 -0
  165. package/session/useOpenFileChange.d.ts +20 -0
  166. package/session/useOpenFileChange.d.ts.map +1 -0
  167. package/session/useOpenFileChange.js +31 -0
  168. package/session/useOpenFileChange.js.map +1 -0
  169. package/session/usePlanDraft.d.ts +52 -0
  170. package/session/usePlanDraft.d.ts.map +1 -0
  171. package/session/usePlanDraft.js +63 -0
  172. package/session/usePlanDraft.js.map +1 -0
  173. package/session/useSessionConversation.d.ts +6 -0
  174. package/session/useSessionConversation.d.ts.map +1 -1
  175. package/session/useSessionConversation.js +1 -0
  176. package/session/useSessionConversation.js.map +1 -1
  177. package/session/useSessionPageFlow.d.ts.map +1 -1
  178. package/session/useSessionPageFlow.js +1 -0
  179. package/session/useSessionPageFlow.js.map +1 -1
  180. package/session/useSessionPanel.d.ts +120 -0
  181. package/session/useSessionPanel.d.ts.map +1 -0
  182. package/session/useSessionPanel.js +130 -0
  183. package/session/useSessionPanel.js.map +1 -0
  184. package/session/useSessionRailViews.d.ts +45 -0
  185. package/session/useSessionRailViews.d.ts.map +1 -0
  186. package/session/useSessionRailViews.js +105 -0
  187. package/session/useSessionRailViews.js.map +1 -0
  188. package/src/composer/SessionComposer.tsx +52 -10
  189. package/src/composer/__tests__/SessionComposer-contract.test.tsx +58 -4
  190. package/src/execution/ArtifactContentRenderer.tsx +101 -24
  191. package/src/execution/FilePathContext.tsx +8 -1
  192. package/src/execution/FilePathLink.tsx +22 -6
  193. package/src/execution/MessageEntry.tsx +111 -1
  194. package/src/execution/MessageThread.tsx +291 -36
  195. package/src/execution/PlanArtifactCard.tsx +63 -31
  196. package/src/execution/PlanDocumentMessage.tsx +96 -0
  197. package/src/execution/PlanStreamingCard.tsx +137 -0
  198. package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +80 -1
  199. package/src/execution/__tests__/FilePathLink.test.tsx +87 -0
  200. package/src/execution/__tests__/MessageThread.test.tsx +101 -0
  201. package/src/execution/__tests__/PlanArtifactCard.test.tsx +66 -12
  202. package/src/execution/__tests__/PlanDocumentMessage.test.tsx +55 -0
  203. package/src/execution/__tests__/PlanStreamingCard.test.tsx +49 -0
  204. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +504 -22
  205. package/src/execution/__tests__/message-entry.test.tsx +48 -0
  206. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +85 -0
  207. package/src/execution/index.ts +7 -1
  208. package/src/execution/useCreateAgentExecution.ts +16 -1
  209. package/src/github/__tests__/decodeGitHubContent.test.ts +102 -0
  210. package/src/github/__tests__/useGitHubFileReader.test.ts +183 -0
  211. package/src/github/decodeGitHubContent.ts +83 -0
  212. package/src/github/index.ts +1 -0
  213. package/src/github/useGitHubFileReader.ts +152 -0
  214. package/src/github/useGitHubTreeLister.ts +6 -2
  215. package/src/index.ts +63 -9
  216. package/src/internal/ResizableSplit.tsx +184 -51
  217. package/src/internal/VirtualizedThread.tsx +12 -4
  218. package/src/internal/__tests__/ResizableSplit.test.tsx +125 -3
  219. package/src/internal/__tests__/markdown-components.test.tsx +68 -0
  220. package/src/internal/__tests__/useRevealLine.test.tsx +97 -0
  221. package/src/internal/file-icons/__tests__/file-icons.test.tsx +35 -0
  222. package/src/internal/file-icons/index.tsx +137 -0
  223. package/src/internal/file-tree/FileTreeNode.tsx +43 -3
  224. package/src/internal/file-tree/__tests__/FileTreeNode-explorer.test.tsx +86 -0
  225. package/src/internal/markdown-components.tsx +116 -11
  226. package/src/{workflow → internal}/motion-preference.ts +7 -4
  227. package/src/internal/store/__tests__/workspace-editors-store.test.ts +216 -0
  228. package/src/internal/store/__tests__/workspace-file-selection-store.test.ts +118 -0
  229. package/src/internal/store/index.ts +94 -0
  230. package/src/internal/store/workspace-editors-store.ts +315 -0
  231. package/src/internal/store/workspace-file-selection-store.ts +88 -0
  232. package/src/internal/useRevealLine.ts +81 -0
  233. package/src/library/__tests__/detect-plan-artifact.test.ts +48 -0
  234. package/src/library/__tests__/detect-streaming-plan.test.ts +234 -0
  235. package/src/library/detect-plan-artifact.ts +35 -0
  236. package/src/library/detect-streaming-plan.ts +108 -0
  237. package/src/library/index.ts +5 -0
  238. package/src/session/NewSessionViewer.tsx +123 -55
  239. package/src/session/PlanEditor.tsx +361 -0
  240. package/src/session/PlanStreamingDocument.tsx +97 -0
  241. package/src/session/SessionPanelChip.tsx +103 -0
  242. package/src/session/SessionViewer.tsx +617 -90
  243. package/src/session/__tests__/NewSessionViewer.test.tsx +148 -0
  244. package/src/session/__tests__/PlanEditor.test.tsx +217 -0
  245. package/src/session/__tests__/PlanStreamingDocument.test.tsx +50 -0
  246. package/src/session/__tests__/SessionPanelChip.test.tsx +42 -0
  247. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +9 -4
  248. package/src/session/__tests__/SessionViewer-transcriptFileOpen.test.tsx +205 -0
  249. package/src/session/__tests__/audienceWiring.test.tsx +55 -19
  250. package/src/session/__tests__/useOpenFileChange.test.tsx +82 -0
  251. package/src/session/__tests__/usePlanDraft.test.ts +102 -0
  252. package/src/session/__tests__/useSessionPanel.test.ts +298 -0
  253. package/src/session/__tests__/useSessionRailViews.test.tsx +124 -0
  254. package/src/session/{inspector → facets}/ArtifactsTab.tsx +27 -5
  255. package/src/session/{inspector → facets}/ChangesTab.tsx +3 -3
  256. package/src/session/{inspector → facets}/SetupTab.tsx +13 -1
  257. package/src/session/{inspector → facets}/UsageTab.tsx +1 -1
  258. package/src/session/{inspector → facets}/__tests__/ArtifactsTab.test.tsx +35 -1
  259. package/src/session/facets/__tests__/SetupTab.test.tsx +44 -0
  260. package/src/session/{inspector → facets}/index.ts +0 -16
  261. package/src/session/index.ts +30 -13
  262. package/src/session/plan-document.ts +19 -0
  263. package/src/session/useOpenFileChange.ts +53 -0
  264. package/src/session/usePlanDraft.ts +109 -0
  265. package/src/session/useSessionConversation.ts +7 -0
  266. package/src/session/useSessionPageFlow.ts +1 -0
  267. package/src/session/useSessionPanel.ts +288 -0
  268. package/src/session/useSessionRailViews.tsx +200 -0
  269. package/src/workflow/WorkflowExecutionGraph.tsx +1 -1
  270. package/src/workflow/index.ts +1 -1
  271. package/src/workflow/useFollowExecution.ts +1 -1
  272. package/src/workspace/EditorTabs.tsx +145 -0
  273. package/src/workspace/ExplorerTree.tsx +247 -0
  274. package/src/workspace/FileViewer.tsx +516 -0
  275. package/src/workspace/WorkspaceContentSearch.tsx +478 -0
  276. package/src/workspace/WorkspaceContentSearcher.ts +70 -0
  277. package/src/workspace/WorkspaceFileLister.ts +11 -0
  278. package/src/workspace/WorkspaceFileReader.ts +57 -0
  279. package/src/workspace/WorkspaceFileSearch.tsx +429 -0
  280. package/src/workspace/WorkspaceSurface.tsx +781 -0
  281. package/src/workspace/__tests__/EditorTabs.test.tsx +100 -0
  282. package/src/workspace/__tests__/ExplorerTree.test.tsx +152 -0
  283. package/src/workspace/__tests__/FileViewer.test.tsx +421 -0
  284. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +196 -0
  285. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +206 -0
  286. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +348 -0
  287. package/src/workspace/__tests__/findChangeForSelection.test.ts +132 -0
  288. package/src/workspace/__tests__/matchContentPreview.test.ts +55 -0
  289. package/src/workspace/__tests__/matchWorkspaceFiles.test.ts +104 -0
  290. package/src/workspace/__tests__/resolveWorkspaceFileSelection.test.ts +135 -0
  291. package/src/workspace/__tests__/useWorkspaceContentSearch.test.tsx +196 -0
  292. package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +190 -0
  293. package/src/workspace/__tests__/useWorkspaceFileSearch.test.tsx +162 -0
  294. package/src/workspace/findChangeForSelection.ts +61 -0
  295. package/src/workspace/index.ts +56 -2
  296. package/src/workspace/matchContentPreview.ts +51 -0
  297. package/src/workspace/matchWorkspaceFiles.ts +86 -0
  298. package/src/workspace/resolveWorkspaceFileSelection.ts +119 -0
  299. package/src/workspace/useWorkspaceContentSearch.ts +228 -0
  300. package/src/workspace/useWorkspaceFileContent.ts +129 -0
  301. package/src/workspace/useWorkspaceFileSearch.ts +184 -0
  302. package/src/workspace/useWorkspaceFiles.ts +27 -27
  303. package/src/workspace/workspaceListingCache.ts +93 -0
  304. package/styles.css +1 -1
  305. package/workflow/WorkflowExecutionGraph.js +1 -1
  306. package/workflow/WorkflowExecutionGraph.js.map +1 -1
  307. package/workflow/index.d.ts +1 -1
  308. package/workflow/index.d.ts.map +1 -1
  309. package/workflow/index.js +1 -1
  310. package/workflow/index.js.map +1 -1
  311. package/workflow/useFollowExecution.js +1 -1
  312. package/workflow/useFollowExecution.js.map +1 -1
  313. package/workspace/EditorTabs.d.ts +29 -0
  314. package/workspace/EditorTabs.d.ts.map +1 -0
  315. package/workspace/EditorTabs.js +66 -0
  316. package/workspace/EditorTabs.js.map +1 -0
  317. package/workspace/ExplorerTree.d.ts +32 -0
  318. package/workspace/ExplorerTree.d.ts.map +1 -0
  319. package/workspace/ExplorerTree.js +45 -0
  320. package/workspace/ExplorerTree.js.map +1 -0
  321. package/workspace/FileViewer.d.ts +93 -0
  322. package/workspace/FileViewer.d.ts.map +1 -0
  323. package/workspace/FileViewer.js +182 -0
  324. package/workspace/FileViewer.js.map +1 -0
  325. package/workspace/WorkspaceContentSearch.d.ts +41 -0
  326. package/workspace/WorkspaceContentSearch.d.ts.map +1 -0
  327. package/workspace/WorkspaceContentSearch.js +195 -0
  328. package/workspace/WorkspaceContentSearch.js.map +1 -0
  329. package/workspace/WorkspaceContentSearcher.d.ts +59 -0
  330. package/workspace/WorkspaceContentSearcher.d.ts.map +1 -0
  331. package/workspace/WorkspaceContentSearcher.js +7 -0
  332. package/workspace/WorkspaceContentSearcher.js.map +1 -0
  333. package/workspace/WorkspaceFileLister.d.ts +11 -0
  334. package/workspace/WorkspaceFileLister.d.ts.map +1 -1
  335. package/workspace/WorkspaceFileReader.d.ts +48 -0
  336. package/workspace/WorkspaceFileReader.d.ts.map +1 -0
  337. package/workspace/WorkspaceFileReader.js +11 -0
  338. package/workspace/WorkspaceFileReader.js.map +1 -0
  339. package/workspace/WorkspaceFileSearch.d.ts +32 -0
  340. package/workspace/WorkspaceFileSearch.d.ts.map +1 -0
  341. package/workspace/WorkspaceFileSearch.js +155 -0
  342. package/workspace/WorkspaceFileSearch.js.map +1 -0
  343. package/workspace/WorkspaceSurface.d.ts +151 -0
  344. package/workspace/WorkspaceSurface.d.ts.map +1 -0
  345. package/workspace/WorkspaceSurface.js +184 -0
  346. package/workspace/WorkspaceSurface.js.map +1 -0
  347. package/workspace/findChangeForSelection.d.ts +19 -0
  348. package/workspace/findChangeForSelection.d.ts.map +1 -0
  349. package/workspace/findChangeForSelection.js +46 -0
  350. package/workspace/findChangeForSelection.js.map +1 -0
  351. package/workspace/index.d.ts +24 -2
  352. package/workspace/index.d.ts.map +1 -1
  353. package/workspace/index.js +13 -1
  354. package/workspace/index.js.map +1 -1
  355. package/workspace/matchContentPreview.d.ts +21 -0
  356. package/workspace/matchContentPreview.d.ts.map +1 -0
  357. package/workspace/matchContentPreview.js +36 -0
  358. package/workspace/matchContentPreview.js.map +1 -0
  359. package/workspace/matchWorkspaceFiles.d.ts +30 -0
  360. package/workspace/matchWorkspaceFiles.d.ts.map +1 -0
  361. package/workspace/matchWorkspaceFiles.js +58 -0
  362. package/workspace/matchWorkspaceFiles.js.map +1 -0
  363. package/workspace/resolveWorkspaceFileSelection.d.ts +19 -0
  364. package/workspace/resolveWorkspaceFileSelection.d.ts.map +1 -0
  365. package/workspace/resolveWorkspaceFileSelection.js +115 -0
  366. package/workspace/resolveWorkspaceFileSelection.js.map +1 -0
  367. package/workspace/useWorkspaceContentSearch.d.ts +69 -0
  368. package/workspace/useWorkspaceContentSearch.d.ts.map +1 -0
  369. package/workspace/useWorkspaceContentSearch.js +154 -0
  370. package/workspace/useWorkspaceContentSearch.js.map +1 -0
  371. package/workspace/useWorkspaceFileContent.d.ts +73 -0
  372. package/workspace/useWorkspaceFileContent.d.ts.map +1 -0
  373. package/workspace/useWorkspaceFileContent.js +57 -0
  374. package/workspace/useWorkspaceFileContent.js.map +1 -0
  375. package/workspace/useWorkspaceFileSearch.d.ts +61 -0
  376. package/workspace/useWorkspaceFileSearch.d.ts.map +1 -0
  377. package/workspace/useWorkspaceFileSearch.js +119 -0
  378. package/workspace/useWorkspaceFileSearch.js.map +1 -0
  379. package/workspace/useWorkspaceFiles.d.ts +13 -5
  380. package/workspace/useWorkspaceFiles.d.ts.map +1 -1
  381. package/workspace/useWorkspaceFiles.js +18 -19
  382. package/workspace/useWorkspaceFiles.js.map +1 -1
  383. package/workspace/workspaceListingCache.d.ts +47 -0
  384. package/workspace/workspaceListingCache.d.ts.map +1 -0
  385. package/workspace/workspaceListingCache.js +55 -0
  386. package/workspace/workspaceListingCache.js.map +1 -0
  387. package/session/inspector/ArtifactsTab.d.ts.map +0 -1
  388. package/session/inspector/ArtifactsTab.js.map +0 -1
  389. package/session/inspector/ChangesTab.d.ts.map +0 -1
  390. package/session/inspector/ChangesTab.js.map +0 -1
  391. package/session/inspector/InspectTab.d.ts.map +0 -1
  392. package/session/inspector/InspectTab.js.map +0 -1
  393. package/session/inspector/SessionInspector.d.ts +0 -64
  394. package/session/inspector/SessionInspector.d.ts.map +0 -1
  395. package/session/inspector/SessionInspector.js +0 -53
  396. package/session/inspector/SessionInspector.js.map +0 -1
  397. package/session/inspector/SetupTab.d.ts.map +0 -1
  398. package/session/inspector/SetupTab.js.map +0 -1
  399. package/session/inspector/UsageTab.d.ts.map +0 -1
  400. package/session/inspector/UsageTab.js.map +0 -1
  401. package/session/inspector/WorkspaceTab.d.ts +0 -35
  402. package/session/inspector/WorkspaceTab.d.ts.map +0 -1
  403. package/session/inspector/WorkspaceTab.js +0 -75
  404. package/session/inspector/WorkspaceTab.js.map +0 -1
  405. package/session/inspector/index.d.ts.map +0 -1
  406. package/session/inspector/index.js.map +0 -1
  407. package/session/inspector/useSessionInspector.d.ts +0 -49
  408. package/session/inspector/useSessionInspector.d.ts.map +0 -1
  409. package/session/inspector/useSessionInspector.js +0 -122
  410. package/session/inspector/useSessionInspector.js.map +0 -1
  411. package/src/session/inspector/SessionInspector.tsx +0 -162
  412. package/src/session/inspector/WorkspaceTab.tsx +0 -245
  413. package/src/session/inspector/__tests__/useSessionInspector.test.ts +0 -251
  414. package/src/session/inspector/useSessionInspector.ts +0 -178
  415. package/src/workspace/WorkspaceEntryFiles.tsx +0 -196
  416. package/workflow/motion-preference.d.ts.map +0 -1
  417. package/workflow/motion-preference.js.map +0 -1
  418. package/workspace/WorkspaceEntryFiles.d.ts +0 -22
  419. package/workspace/WorkspaceEntryFiles.d.ts.map +0 -1
  420. package/workspace/WorkspaceEntryFiles.js +0 -58
  421. package/workspace/WorkspaceEntryFiles.js.map +0 -1
  422. /package/session/{inspector → facets}/InspectTab.d.ts +0 -0
  423. /package/session/{inspector → facets}/InspectTab.js +0 -0
  424. /package/src/{workflow → internal}/__tests__/motion-preference.test.ts +0 -0
  425. /package/src/session/{inspector → facets}/InspectTab.tsx +0 -0
  426. /package/src/session/{inspector → facets}/__tests__/ChangesTab.test.tsx +0 -0
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, cleanup, fireEvent } from "@testing-library/react";
3
+ import { EditorTabs } from "../EditorTabs";
4
+ import { editorKey, type OpenEditor } from "../../internal/store/index";
5
+
6
+ const editors: OpenEditor[] = [
7
+ { entryId: "e1", path: "src/a.ts", preview: false },
8
+ { entryId: "e1", path: "src/b.ts", preview: true },
9
+ ];
10
+
11
+ function renderTabs(
12
+ overrides: Partial<React.ComponentProps<typeof EditorTabs>> = {},
13
+ ) {
14
+ const onActivate = vi.fn();
15
+ const onPin = vi.fn();
16
+ const onClose = vi.fn();
17
+ render(
18
+ <EditorTabs
19
+ editors={editors}
20
+ activeKey={editorKey("e1", "src/a.ts")}
21
+ onActivate={onActivate}
22
+ onPin={onPin}
23
+ onClose={onClose}
24
+ {...overrides}
25
+ />,
26
+ );
27
+ return { onActivate, onPin, onClose };
28
+ }
29
+
30
+ afterEach(() => cleanup());
31
+
32
+ describe("EditorTabs", () => {
33
+ it("renders a tab per editor with basenames", () => {
34
+ renderTabs();
35
+ expect(screen.getByRole("tab", { name: /a\.ts/ })).toBeTruthy();
36
+ expect(screen.getByRole("tab", { name: /b\.ts/ })).toBeTruthy();
37
+ });
38
+
39
+ it("marks the active tab aria-selected", () => {
40
+ renderTabs();
41
+ expect(
42
+ screen.getByRole("tab", { name: /a\.ts/ }).getAttribute("aria-selected"),
43
+ ).toBe("true");
44
+ expect(
45
+ screen.getByRole("tab", { name: /b\.ts/ }).getAttribute("aria-selected"),
46
+ ).toBe("false");
47
+ });
48
+
49
+ it("renders the preview tab label in italics", () => {
50
+ renderTabs();
51
+ // b.ts is the preview tab; its label span carries the italic class.
52
+ const label = screen.getByText("b.ts");
53
+ expect(label.className).toContain("italic");
54
+ const pinnedLabel = screen.getByText("a.ts");
55
+ expect(pinnedLabel.className).not.toContain("italic");
56
+ });
57
+
58
+ it("activates a tab on click", () => {
59
+ const { onActivate } = renderTabs();
60
+ fireEvent.click(screen.getByRole("tab", { name: /b\.ts/ }));
61
+ expect(onActivate).toHaveBeenCalledWith("e1", "src/b.ts");
62
+ });
63
+
64
+ it("pins a tab on double click", () => {
65
+ const { onPin } = renderTabs();
66
+ fireEvent.doubleClick(screen.getByRole("tab", { name: /b\.ts/ }));
67
+ expect(onPin).toHaveBeenCalledWith("e1", "src/b.ts");
68
+ });
69
+
70
+ it("closes a tab via its close button", () => {
71
+ const { onClose } = renderTabs();
72
+ fireEvent.click(screen.getByRole("button", { name: "Close a.ts" }));
73
+ expect(onClose).toHaveBeenCalledWith("e1", "src/a.ts");
74
+ });
75
+
76
+ it("closes a tab on middle click", () => {
77
+ const { onClose } = renderTabs();
78
+ fireEvent(
79
+ screen.getByRole("tab", { name: /a\.ts/ }),
80
+ new MouseEvent("auxclick", { bubbles: true, button: 1 }),
81
+ );
82
+ expect(onClose).toHaveBeenCalledWith("e1", "src/a.ts");
83
+ });
84
+
85
+ it("moves activation with arrow keys", () => {
86
+ const { onActivate } = renderTabs();
87
+ fireEvent.keyDown(screen.getByRole("tab", { name: /a\.ts/ }), {
88
+ key: "ArrowRight",
89
+ });
90
+ expect(onActivate).toHaveBeenCalledWith("e1", "src/b.ts");
91
+ });
92
+
93
+ it("closes the focused tab on Delete", () => {
94
+ const { onClose } = renderTabs();
95
+ fireEvent.keyDown(screen.getByRole("tab", { name: /a\.ts/ }), {
96
+ key: "Delete",
97
+ });
98
+ expect(onClose).toHaveBeenCalledWith("e1", "src/a.ts");
99
+ });
100
+ });
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, cleanup } from "@testing-library/react";
3
+ import { ExplorerTree } from "../ExplorerTree";
4
+ import type { WorkspaceEntry } from "../useWorkspaceEntries";
5
+ import type { WorkspaceFileLister } from "../WorkspaceFileLister";
6
+
7
+ afterEach(cleanup);
8
+
9
+ // Unique entry ids per test — `useWorkspaceFiles` keys a module-level cache by
10
+ // entry.id, so reusing ids would leak listings across tests.
11
+ function entry(id: string, name = "acme/repo"): WorkspaceEntry {
12
+ return {
13
+ id,
14
+ name,
15
+ type: "git",
16
+ gitUrl: "https://github.com/acme/repo",
17
+ gitBranch: "main",
18
+ };
19
+ }
20
+
21
+ function lister(paths: string[]): WorkspaceFileLister {
22
+ return vi.fn(async () => paths.map((path) => ({ path, isDirectory: false })));
23
+ }
24
+
25
+ describe("ExplorerTree", () => {
26
+ it("renders a root header and its top-level files", async () => {
27
+ render(
28
+ <ExplorerTree
29
+ entries={[entry("x1")]}
30
+ lister={lister(["a.ts", "README.md"])}
31
+ selectedFile={null}
32
+ onOpenFile={vi.fn()}
33
+ onActivateFile={vi.fn()}
34
+ />,
35
+ );
36
+ expect(screen.getByText("acme/repo")).toBeTruthy();
37
+ expect(await screen.findByText("a.ts")).toBeTruthy();
38
+ expect(screen.getByText("README.md")).toBeTruthy();
39
+ });
40
+
41
+ it("starts folders collapsed, revealing children on expand", async () => {
42
+ render(
43
+ <ExplorerTree
44
+ entries={[entry("x1b")]}
45
+ lister={lister(["src/nested.ts"])}
46
+ selectedFile={null}
47
+ onOpenFile={vi.fn()}
48
+ onActivateFile={vi.fn()}
49
+ />,
50
+ );
51
+ const folder = await screen.findByText("src");
52
+ expect(screen.queryByText("nested.ts")).toBeNull();
53
+ fireEvent.click(folder);
54
+ expect(await screen.findByText("nested.ts")).toBeTruthy();
55
+ });
56
+
57
+ it("opens a file (preview) on single click", async () => {
58
+ const onOpenFile = vi.fn();
59
+ render(
60
+ <ExplorerTree
61
+ entries={[entry("x2")]}
62
+ lister={lister(["a.ts"])}
63
+ selectedFile={null}
64
+ onOpenFile={onOpenFile}
65
+ onActivateFile={vi.fn()}
66
+ />,
67
+ );
68
+ fireEvent.click(await screen.findByText("a.ts"));
69
+ expect(onOpenFile).toHaveBeenCalledWith("x2", "a.ts");
70
+ });
71
+
72
+ it("pins a file (activate) on double click", async () => {
73
+ const onActivateFile = vi.fn();
74
+ render(
75
+ <ExplorerTree
76
+ entries={[entry("x3")]}
77
+ lister={lister(["a.ts"])}
78
+ selectedFile={null}
79
+ onOpenFile={vi.fn()}
80
+ onActivateFile={onActivateFile}
81
+ />,
82
+ );
83
+ fireEvent.doubleClick(await screen.findByText("a.ts"));
84
+ expect(onActivateFile).toHaveBeenCalledWith("x3", "a.ts");
85
+ });
86
+
87
+ it("highlights the selected file scoped to its entry", async () => {
88
+ render(
89
+ <ExplorerTree
90
+ entries={[entry("x4")]}
91
+ lister={lister(["a.ts", "b.ts"])}
92
+ selectedFile={{ entryId: "x4", path: "a.ts" }}
93
+ onOpenFile={vi.fn()}
94
+ onActivateFile={vi.fn()}
95
+ />,
96
+ );
97
+ const selected = await screen.findByText("a.ts");
98
+ const other = screen.getByText("b.ts");
99
+ expect(selected.closest("button")?.getAttribute("aria-current")).toBe("true");
100
+ expect(other.closest("button")?.getAttribute("aria-current")).toBeNull();
101
+ });
102
+
103
+ it("collapses all but the first root by default", async () => {
104
+ render(
105
+ <ExplorerTree
106
+ entries={[entry("x5a", "first"), entry("x5b", "second")]}
107
+ lister={lister(["a.ts"])}
108
+ selectedFile={null}
109
+ onOpenFile={vi.fn()}
110
+ onActivateFile={vi.fn()}
111
+ />,
112
+ );
113
+ // First root is expanded (its file shows); second root header shows but its
114
+ // tree is not fetched/rendered until expanded.
115
+ expect(await screen.findByText("a.ts")).toBeTruthy();
116
+ const second = screen.getByText("second").closest("button")!;
117
+ expect(second.getAttribute("aria-expanded")).toBe("false");
118
+ });
119
+
120
+ it("offers a remove control per root when onRemoveEntry is provided", async () => {
121
+ const onRemoveEntry = vi.fn();
122
+ render(
123
+ <ExplorerTree
124
+ entries={[entry("x6")]}
125
+ lister={lister(["a.ts"])}
126
+ selectedFile={null}
127
+ onOpenFile={vi.fn()}
128
+ onActivateFile={vi.fn()}
129
+ onRemoveEntry={onRemoveEntry}
130
+ />,
131
+ );
132
+ fireEvent.click(
133
+ screen.getByRole("button", { name: "Remove acme/repo from workspace" }),
134
+ );
135
+ expect(onRemoveEntry).toHaveBeenCalledWith("x6");
136
+ });
137
+
138
+ it("is browse-only (no remove controls) without onRemoveEntry", () => {
139
+ render(
140
+ <ExplorerTree
141
+ entries={[entry("x7")]}
142
+ lister={lister(["a.ts"])}
143
+ selectedFile={null}
144
+ onOpenFile={vi.fn()}
145
+ onActivateFile={vi.fn()}
146
+ />,
147
+ );
148
+ expect(
149
+ screen.queryByRole("button", { name: /Remove .* from workspace/ }),
150
+ ).toBeNull();
151
+ });
152
+ });
@@ -0,0 +1,421 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react";
3
+ import { create } from "@bufbuild/protobuf";
4
+ import {
5
+ FileChangeSchema,
6
+ FileContentSchema,
7
+ type FileChange,
8
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
9
+ import {
10
+ FileChangeCaptureLevel,
11
+ FileChangeType,
12
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
13
+ import type { UseFileChangeContentReturn } from "../../execution/useFileChangeContent";
14
+
15
+ // Drive FileChangeDiff's content deterministically (it is rendered by the diff
16
+ // pane) — same seam FileChangesView's own tests mock.
17
+ let mockFileChangeContent: UseFileChangeContentReturn;
18
+ vi.mock("../../execution/useFileChangeContent", () => ({
19
+ useFileChangeContent: () => mockFileChangeContent,
20
+ }));
21
+
22
+ const { FileViewer } = await import("../FileViewer");
23
+
24
+ afterEach(cleanup);
25
+ beforeEach(() => {
26
+ mockFileChangeContent = {
27
+ beforeText: "alpha\n",
28
+ afterText: "beta\n",
29
+ isBinary: false,
30
+ isLoading: false,
31
+ error: null,
32
+ isTruncated: false,
33
+ downloadUrl: null,
34
+ };
35
+ });
36
+ import type { WorkspaceEntry } from "../useWorkspaceEntries";
37
+ import type {
38
+ WorkspaceFileContent,
39
+ WorkspaceFileReader,
40
+ } from "../WorkspaceFileReader";
41
+
42
+ function wholeFileChange(path: string): FileChange {
43
+ return create(FileChangeSchema, {
44
+ path,
45
+ changeType: FileChangeType.MODIFY,
46
+ captureLevel: FileChangeCaptureLevel.WHOLE_FILE,
47
+ before: create(FileContentSchema, { body: { case: "inline", value: "alpha\n" } }),
48
+ after: create(FileContentSchema, { body: { case: "inline", value: "beta\n" } }),
49
+ });
50
+ }
51
+
52
+ function deleteChange(path: string): FileChange {
53
+ return create(FileChangeSchema, {
54
+ path,
55
+ changeType: FileChangeType.DELETE,
56
+ captureLevel: FileChangeCaptureLevel.WHOLE_FILE,
57
+ before: create(FileContentSchema, { body: { case: "inline", value: "alpha\n" } }),
58
+ });
59
+ }
60
+
61
+ function gitEntry(id = "e1"): WorkspaceEntry {
62
+ return {
63
+ id,
64
+ name: "acme/repo",
65
+ type: "git",
66
+ gitUrl: "https://github.com/acme/repo",
67
+ gitBranch: "main",
68
+ };
69
+ }
70
+
71
+ const ENTRIES = [gitEntry()];
72
+
73
+ function readerFor(result: WorkspaceFileContent | null): WorkspaceFileReader {
74
+ return vi.fn(async () => result);
75
+ }
76
+
77
+ function text(t: string): WorkspaceFileContent {
78
+ return { text: t, isBinary: false, size: t.length, encoding: "utf-8" };
79
+ }
80
+
81
+ describe("FileViewer", () => {
82
+ it("shows the unsupported state when no reader is injected", () => {
83
+ render(
84
+ <FileViewer
85
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
86
+ entries={ENTRIES}
87
+ reader={undefined}
88
+ />,
89
+ );
90
+ expect(screen.getByText(/Preview isn.t available/i)).toBeTruthy();
91
+ });
92
+
93
+ it("shows a loading skeleton while the read is in flight", () => {
94
+ const reader: WorkspaceFileReader = () => new Promise(() => {});
95
+ render(
96
+ <FileViewer
97
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
98
+ entries={ENTRIES}
99
+ reader={reader}
100
+ />,
101
+ );
102
+ expect(screen.getByLabelText("Loading file")).toBeTruthy();
103
+ });
104
+
105
+ it("renders displayable text via the shared renderer", async () => {
106
+ render(
107
+ <FileViewer
108
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
109
+ entries={ENTRIES}
110
+ reader={readerFor(text("const answer = 42;"))}
111
+ />,
112
+ );
113
+ await waitFor(() =>
114
+ expect(screen.getByText("const answer = 42;")).toBeTruthy(),
115
+ );
116
+ });
117
+
118
+ it("shows the binary state for binary content", async () => {
119
+ render(
120
+ <FileViewer
121
+ selectedFile={{ entryId: "e1", path: "logo.png" }}
122
+ entries={ENTRIES}
123
+ reader={readerFor({ text: null, isBinary: true, size: 2048, encoding: "unknown" })}
124
+ />,
125
+ );
126
+ await waitFor(() => expect(screen.getByText(/Binary file/i)).toBeTruthy());
127
+ });
128
+
129
+ it("shows the too-large state for a size-capped read with no text", async () => {
130
+ render(
131
+ <FileViewer
132
+ selectedFile={{ entryId: "e1", path: "big.log" }}
133
+ entries={ENTRIES}
134
+ reader={readerFor({
135
+ text: null,
136
+ isBinary: false,
137
+ size: 20 * 1024 * 1024,
138
+ encoding: "none",
139
+ truncated: true,
140
+ })}
141
+ />,
142
+ );
143
+ await waitFor(() =>
144
+ expect(screen.getByText(/too large to preview/i)).toBeTruthy(),
145
+ );
146
+ });
147
+
148
+ it("shows the undecodable state when text is null without truncation or binary", async () => {
149
+ render(
150
+ <FileViewer
151
+ selectedFile={{ entryId: "e1", path: "weird.dat" }}
152
+ entries={ENTRIES}
153
+ reader={readerFor({ text: null, isBinary: false, size: 5, encoding: "unknown" })}
154
+ />,
155
+ );
156
+ await waitFor(() =>
157
+ expect(screen.getByText(/can.t be displayed as text/i)).toBeTruthy(),
158
+ );
159
+ });
160
+
161
+ it("shows the error state with a working Retry", async () => {
162
+ let attempt = 0;
163
+ const reader: WorkspaceFileReader = vi.fn(async () => {
164
+ attempt += 1;
165
+ if (attempt === 1) throw new Error("network down");
166
+ return text("recovered");
167
+ });
168
+ render(
169
+ <FileViewer
170
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
171
+ entries={ENTRIES}
172
+ reader={reader}
173
+ />,
174
+ );
175
+
176
+ await waitFor(() => expect(screen.getByText("network down")).toBeTruthy());
177
+
178
+ fireEvent.click(screen.getByText("Retry"));
179
+
180
+ await waitFor(() => expect(screen.getByText("recovered")).toBeTruthy());
181
+ expect(reader).toHaveBeenCalledTimes(2);
182
+ });
183
+
184
+ it("shows the empty state when the owning entry has been removed", () => {
185
+ render(
186
+ <FileViewer
187
+ selectedFile={{ entryId: "gone", path: "src/a.ts" }}
188
+ entries={ENTRIES}
189
+ reader={readerFor(text("x"))}
190
+ />,
191
+ );
192
+ expect(screen.getByText(/no longer in the workspace/i)).toBeTruthy();
193
+ });
194
+
195
+ it("renders the basename and directory in the header", async () => {
196
+ render(
197
+ <FileViewer
198
+ selectedFile={{ entryId: "e1", path: "src/deep/module.ts" }}
199
+ entries={ENTRIES}
200
+ reader={readerFor(text("ok"))}
201
+ />,
202
+ );
203
+ expect(screen.getByText("module.ts")).toBeTruthy();
204
+ expect(screen.getByText("src/deep")).toBeTruthy();
205
+ });
206
+
207
+ it("invokes onClose from the close button", async () => {
208
+ const onClose = vi.fn();
209
+ render(
210
+ <FileViewer
211
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
212
+ entries={ENTRIES}
213
+ reader={readerFor(text("ok"))}
214
+ onClose={onClose}
215
+ />,
216
+ );
217
+ fireEvent.click(screen.getByLabelText("Close file viewer"));
218
+ expect(onClose).toHaveBeenCalledTimes(1);
219
+ });
220
+
221
+ // --- no `change` prop: unchanged browse-only behavior (regression) --------
222
+
223
+ it("shows no view toggle when no change is provided", async () => {
224
+ render(
225
+ <FileViewer
226
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
227
+ entries={ENTRIES}
228
+ reader={readerFor(text("plain content"))}
229
+ />,
230
+ );
231
+ await waitFor(() => expect(screen.getByText("plain content")).toBeTruthy());
232
+ expect(screen.queryByRole("radiogroup", { name: "File view" })).toBeNull();
233
+ expect(screen.queryByText(/Live — may differ/i)).toBeNull();
234
+ });
235
+ });
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Diff mode (Slice 4) — a changed file defaults to its authoritative diff
239
+ // ---------------------------------------------------------------------------
240
+
241
+ describe("FileViewer — diff mode", () => {
242
+ it("defaults a changed file to the Diff view", async () => {
243
+ render(
244
+ <FileViewer
245
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
246
+ entries={ENTRIES}
247
+ reader={readerFor(text("live content"))}
248
+ change={wholeFileChange("src/a.ts")}
249
+ />,
250
+ );
251
+
252
+ const group = screen.getByRole("radiogroup", { name: "File view" });
253
+ const diffRadio = screen.getByRole("radio", { name: "Diff" });
254
+ expect(diffRadio.getAttribute("aria-checked")).toBe("true");
255
+ expect(group).toBeTruthy();
256
+ // The diff (added side) renders; the live file body does not.
257
+ await waitFor(() => expect(screen.getByText("beta")).toBeTruthy());
258
+ expect(screen.queryByText("live content")).toBeNull();
259
+ // Refresh is meaningless for an immutable captured diff.
260
+ expect(screen.queryByLabelText("Reload a.ts")).toBeNull();
261
+ });
262
+
263
+ it("toggles to the live File view, showing the live caption and content", async () => {
264
+ render(
265
+ <FileViewer
266
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
267
+ entries={ENTRIES}
268
+ reader={readerFor(text("live content"))}
269
+ change={wholeFileChange("src/a.ts")}
270
+ />,
271
+ );
272
+
273
+ fireEvent.click(screen.getByRole("radio", { name: "File" }));
274
+
275
+ await waitFor(() => expect(screen.getByText("live content")).toBeTruthy());
276
+ expect(screen.getByText(/Live — may differ from the reviewed change/i)).toBeTruthy();
277
+ expect(screen.getByRole("radio", { name: "File" }).getAttribute("aria-checked")).toBe("true");
278
+ // Refresh returns in the live view.
279
+ expect(screen.getByLabelText("Reload a.ts")).toBeTruthy();
280
+ });
281
+
282
+ it("omits the File option when no reader supports the substrate (diff only)", async () => {
283
+ render(
284
+ <FileViewer
285
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
286
+ entries={ENTRIES}
287
+ reader={undefined}
288
+ change={wholeFileChange("src/a.ts")}
289
+ />,
290
+ );
291
+ await waitFor(() => expect(screen.getByText("beta")).toBeTruthy());
292
+ expect(screen.queryByRole("radiogroup", { name: "File view" })).toBeNull();
293
+ });
294
+
295
+ it("omits the File option for a DELETE change (no current bytes)", async () => {
296
+ render(
297
+ <FileViewer
298
+ selectedFile={{ entryId: "e1", path: "src/gone.ts" }}
299
+ entries={ENTRIES}
300
+ reader={readerFor(text("should not be reachable"))}
301
+ change={deleteChange("src/gone.ts")}
302
+ />,
303
+ );
304
+ await waitFor(() => expect(screen.getByText("alpha")).toBeTruthy());
305
+ expect(screen.queryByRole("radiogroup", { name: "File view" })).toBeNull();
306
+ });
307
+
308
+ it("supports arrow-key roving between Diff and File (radiogroup a11y)", async () => {
309
+ render(
310
+ <FileViewer
311
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
312
+ entries={ENTRIES}
313
+ reader={readerFor(text("live content"))}
314
+ change={wholeFileChange("src/a.ts")}
315
+ />,
316
+ );
317
+
318
+ const diffRadio = screen.getByRole("radio", { name: "Diff" });
319
+ fireEvent.keyDown(diffRadio, { key: "ArrowRight" });
320
+ await waitFor(() =>
321
+ expect(screen.getByRole("radio", { name: "File" }).getAttribute("aria-checked")).toBe("true"),
322
+ );
323
+
324
+ fireEvent.keyDown(screen.getByRole("radio", { name: "File" }), { key: "ArrowLeft" });
325
+ await waitFor(() =>
326
+ expect(screen.getByRole("radio", { name: "Diff" }).getAttribute("aria-checked")).toBe("true"),
327
+ );
328
+ });
329
+ });
330
+
331
+ // ---------------------------------------------------------------------------
332
+ // Reveal (jump-to-line) — a line target opens File view and scrolls to it
333
+ // ---------------------------------------------------------------------------
334
+
335
+ describe("FileViewer — reveal (jump-to-line)", () => {
336
+ let scrollIntoView: ReturnType<typeof vi.fn>;
337
+ beforeEach(() => {
338
+ scrollIntoView = vi.fn();
339
+ Element.prototype.scrollIntoView =
340
+ scrollIntoView as unknown as typeof Element.prototype.scrollIntoView;
341
+ });
342
+
343
+ it("opens a changed file in the live File view when a reveal is present (DR-1)", async () => {
344
+ render(
345
+ <FileViewer
346
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
347
+ entries={ENTRIES}
348
+ reader={readerFor(text("live content"))}
349
+ change={wholeFileChange("src/a.ts")}
350
+ reveal={{ line: 1, nonce: 1 }}
351
+ />,
352
+ );
353
+
354
+ // File view wins over the diff-default, but the Diff toggle stays available.
355
+ expect(screen.getByRole("radio", { name: "File" }).getAttribute("aria-checked")).toBe("true");
356
+ expect(screen.getByRole("radio", { name: "Diff" })).toBeTruthy();
357
+ await waitFor(() => expect(screen.getByText("live content")).toBeTruthy());
358
+ });
359
+
360
+ it("highlights the revealed line and scrolls to it", async () => {
361
+ const { container } = render(
362
+ <FileViewer
363
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
364
+ entries={ENTRIES}
365
+ reader={readerFor(text("one\ntwo\nthree"))}
366
+ reveal={{ line: 2, nonce: 1 }}
367
+ />,
368
+ );
369
+ await waitFor(() => expect(container.querySelector('[data-line="2"]')).not.toBeNull());
370
+ expect(container.querySelector('[data-line="2"]')?.className).toContain(
371
+ "bg-primary-subtle",
372
+ );
373
+ expect(scrollIntoView).toHaveBeenCalled();
374
+ });
375
+
376
+ it("re-forces the File view when a new reveal nonce arrives after a manual toggle", async () => {
377
+ const { rerender } = render(
378
+ <FileViewer
379
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
380
+ entries={ENTRIES}
381
+ reader={readerFor(text("live content"))}
382
+ change={wholeFileChange("src/a.ts")}
383
+ reveal={{ line: 1, nonce: 1 }}
384
+ />,
385
+ );
386
+ // User flips to Diff.
387
+ fireEvent.click(screen.getByRole("radio", { name: "Diff" }));
388
+ await waitFor(() =>
389
+ expect(screen.getByRole("radio", { name: "Diff" }).getAttribute("aria-checked")).toBe("true"),
390
+ );
391
+
392
+ // A second search hit in the same (already-mounted) file bumps the nonce.
393
+ rerender(
394
+ <FileViewer
395
+ selectedFile={{ entryId: "e1", path: "src/a.ts" }}
396
+ entries={ENTRIES}
397
+ reader={readerFor(text("live content"))}
398
+ change={wholeFileChange("src/a.ts")}
399
+ reveal={{ line: 1, nonce: 2 }}
400
+ />,
401
+ );
402
+ await waitFor(() =>
403
+ expect(screen.getByRole("radio", { name: "File" }).getAttribute("aria-checked")).toBe("true"),
404
+ );
405
+ });
406
+
407
+ it("is a no-op for a DELETE change (no live bytes to anchor)", async () => {
408
+ render(
409
+ <FileViewer
410
+ selectedFile={{ entryId: "e1", path: "src/gone.ts" }}
411
+ entries={ENTRIES}
412
+ reader={readerFor(text("unused"))}
413
+ change={deleteChange("src/gone.ts")}
414
+ reveal={{ line: 1, nonce: 1 }}
415
+ />,
416
+ );
417
+ // Falls back to the diff (deleted side); no view toggle, no crash.
418
+ await waitFor(() => expect(screen.getByText("alpha")).toBeTruthy());
419
+ expect(screen.queryByRole("radiogroup", { name: "File view" })).toBeNull();
420
+ });
421
+ });