@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,348 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render, screen, cleanup, fireEvent, within } from "@testing-library/react";
3
+ import { forwardRef } from "react";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // WorkspaceSurface composes the activity rail (Explorer / Search), a resizable
7
+ // sidebar, and the read-only editor. The explorer tree, search, and viewer are
8
+ // exercised by their own suites; here we mock them as probes and prove the
9
+ // surface's own composition — rail switching, the editor tabs/toolbar, collapse,
10
+ // breadcrumbs, and that the active file (+ its change) reaches a chrome-less
11
+ // FileViewer.
12
+ // ---------------------------------------------------------------------------
13
+
14
+ type CapturedProps = Record<string, unknown>;
15
+
16
+ const viewerProps: CapturedProps[] = [];
17
+ vi.mock("../FileViewer", () => ({
18
+ FileViewer: forwardRef((props: CapturedProps, _ref) => {
19
+ viewerProps.push(props);
20
+ return <div data-testid="viewer-probe" />;
21
+ }),
22
+ }));
23
+
24
+ vi.mock("../ExplorerTree", () => ({
25
+ ExplorerTree: () => <div data-testid="explorer-probe" />,
26
+ }));
27
+
28
+ vi.mock("../WorkspaceFileSearch", () => ({
29
+ WorkspaceFileSearch: () => <div data-testid="search-probe" />,
30
+ }));
31
+
32
+ vi.mock("../WorkspaceContentSearch", () => ({
33
+ WorkspaceContentSearch: () => <div data-testid="content-search-probe" />,
34
+ }));
35
+
36
+ import { WorkspaceSurface } from "../WorkspaceSurface";
37
+ import { virtualEntryId } from "../../internal/store/index.js";
38
+ import type { WorkspaceEntry } from "../useWorkspaceEntries";
39
+ import type { WorkspaceFileLister } from "../WorkspaceFileLister";
40
+ import type { WorkspaceFileReader } from "../WorkspaceFileReader";
41
+ import type { WorkspaceContentSearcher } from "../WorkspaceContentSearcher";
42
+
43
+ const gitEntry: WorkspaceEntry = {
44
+ id: "e1",
45
+ name: "acme/app",
46
+ type: "git",
47
+ gitUrl: "https://github.com/acme/app.git",
48
+ gitBranch: "main",
49
+ } as WorkspaceEntry;
50
+
51
+ const lister: WorkspaceFileLister = vi.fn(async () => []) as unknown as WorkspaceFileLister;
52
+ const reader: WorkspaceFileReader = vi.fn(async () => null) as unknown as WorkspaceFileReader;
53
+ const searcher: WorkspaceContentSearcher = vi.fn(async () => ({
54
+ matches: [],
55
+ truncated: false,
56
+ })) as unknown as WorkspaceContentSearcher;
57
+
58
+ function renderSurface(overrides: Partial<React.ComponentProps<typeof WorkspaceSurface>> = {}) {
59
+ const onOpenFile = vi.fn();
60
+ const onActivateEditor = vi.fn();
61
+ const onPinEditor = vi.fn();
62
+ const onCloseEditor = vi.fn();
63
+ const onCollapse = vi.fn();
64
+ render(
65
+ <WorkspaceSurface
66
+ entries={[gitEntry]}
67
+ lister={lister}
68
+ reader={reader}
69
+ editors={[{ entryId: "e1", path: "src/main.go", preview: false }]}
70
+ selectedFile={{ entryId: "e1", path: "src/main.go" }}
71
+ onOpenFile={onOpenFile}
72
+ onActivateEditor={onActivateEditor}
73
+ onPinEditor={onPinEditor}
74
+ onCloseEditor={onCloseEditor}
75
+ onCollapse={onCollapse}
76
+ {...overrides}
77
+ />,
78
+ );
79
+ return { onOpenFile, onActivateEditor, onPinEditor, onCloseEditor, onCollapse };
80
+ }
81
+
82
+ beforeEach(() => {
83
+ viewerProps.length = 0;
84
+ });
85
+ afterEach(() => {
86
+ cleanup();
87
+ vi.clearAllMocks();
88
+ });
89
+
90
+ describe("WorkspaceSurface", () => {
91
+ it("renders the activity rail with Explorer selected by default", () => {
92
+ renderSurface();
93
+ const explorer = screen.getByRole("radio", { name: "Explorer" });
94
+ const search = screen.getByRole("radio", { name: "Search" });
95
+ expect(explorer.getAttribute("aria-checked")).toBe("true");
96
+ expect(search.getAttribute("aria-checked")).toBe("false");
97
+ // Explorer view: the explorer tree renders; the search surface does not.
98
+ expect(screen.getByTestId("explorer-probe")).toBeTruthy();
99
+ expect(screen.queryByTestId("search-probe")).toBeNull();
100
+ });
101
+
102
+ it("switches to the search surface when Search is selected", () => {
103
+ renderSurface();
104
+ fireEvent.click(screen.getByRole("radio", { name: "Search" }));
105
+ expect(screen.getByTestId("search-probe")).toBeTruthy();
106
+ expect(screen.queryByTestId("explorer-probe")).toBeNull();
107
+ });
108
+
109
+ it("shows only filename search (no Name|Text toggle) when no content searcher is injected", () => {
110
+ renderSurface();
111
+ fireEvent.click(screen.getByRole("radio", { name: "Search" }));
112
+ expect(screen.queryByRole("radiogroup", { name: "Search mode" })).toBeNull();
113
+ expect(screen.getByTestId("search-probe")).toBeTruthy();
114
+ expect(screen.queryByTestId("content-search-probe")).toBeNull();
115
+ });
116
+
117
+ it("offers a Name|Text toggle when a content searcher is injected, routing Text to content search", () => {
118
+ renderSurface({ searcher });
119
+ fireEvent.click(screen.getByRole("radio", { name: "Search" }));
120
+
121
+ const toggle = screen.getByRole("radiogroup", { name: "Search mode" });
122
+ expect(toggle).toBeTruthy();
123
+ // Filename search is the default mode.
124
+ expect(screen.getByTestId("search-probe")).toBeTruthy();
125
+ expect(screen.queryByTestId("content-search-probe")).toBeNull();
126
+
127
+ // Switching to Text routes to the content-search surface.
128
+ fireEvent.click(within(toggle).getByRole("radio", { name: "Text" }));
129
+ expect(screen.getByTestId("content-search-probe")).toBeTruthy();
130
+ expect(screen.queryByTestId("search-probe")).toBeNull();
131
+ });
132
+
133
+ it("hands the open file (and its change) to a chrome-less FileViewer", () => {
134
+ const change = { path: "src/main.go" } as never;
135
+ renderSurface({ change });
136
+ const props = viewerProps.at(-1);
137
+ expect(props?.selectedFile).toEqual({ entryId: "e1", path: "src/main.go" });
138
+ expect(props?.showHeader).toBe(false);
139
+ expect(props?.change).toBe(change);
140
+ });
141
+
142
+ it("forwards a jump-to-line reveal to the FileViewer", () => {
143
+ const reveal = { line: 12, nonce: 3 };
144
+ renderSurface({ reveal });
145
+ expect(viewerProps.at(-1)?.reveal).toBe(reveal);
146
+ });
147
+
148
+ it("shows the open file as a tab", () => {
149
+ renderSurface();
150
+ const tab = screen.getByRole("tab", { name: /main\.go/ });
151
+ expect(tab).toBeTruthy();
152
+ expect(tab.getAttribute("aria-selected")).toBe("true");
153
+ });
154
+
155
+ it("collapses back to chat when the toolbar control is clicked", () => {
156
+ const { onCollapse } = renderSurface();
157
+ fireEvent.click(screen.getByRole("button", { name: "Back to chat" }));
158
+ expect(onCollapse).toHaveBeenCalledTimes(1);
159
+ });
160
+
161
+ it("shows an empty editor when no file is selected", () => {
162
+ renderSurface({ selectedFile: null, editors: [] });
163
+ expect(viewerProps.length).toBe(0);
164
+ expect(screen.getByText("Select a file to view its contents.")).toBeTruthy();
165
+ });
166
+ });
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // Host-injected rail views (extraViews) — the unified-panel extension seam
170
+ // ---------------------------------------------------------------------------
171
+
172
+ const configView = {
173
+ id: "configure",
174
+ label: "Config",
175
+ icon: <span data-testid="config-icon" />,
176
+ content: <div data-testid="config-probe" />,
177
+ };
178
+
179
+ const changesView = {
180
+ id: "changes",
181
+ label: "Changes",
182
+ icon: <span />,
183
+ badge: 3,
184
+ content: <div data-testid="changes-probe" />,
185
+ };
186
+
187
+ describe("WorkspaceSurface extraViews", () => {
188
+ it("renders injected views in the rail after the built-ins", () => {
189
+ renderSurface({ extraViews: [configView, changesView] });
190
+ const radios = screen.getAllByRole("radio");
191
+ expect(radios.map((r) => r.getAttribute("title"))).toEqual([
192
+ "Explorer",
193
+ "Search",
194
+ "Config",
195
+ "Changes",
196
+ ]);
197
+ });
198
+
199
+ it("shows a count badge on a rail view and folds it into the accessible name", () => {
200
+ renderSurface({ extraViews: [changesView] });
201
+ const changes = screen.getByRole("radio", { name: "Changes (3)" });
202
+ expect(changes.textContent).toContain("3");
203
+ });
204
+
205
+ it("renders an extra view's content in the sidebar (with heading) when selected, keeping the editor area", () => {
206
+ renderSurface({ extraViews: [configView] });
207
+ fireEvent.click(screen.getByRole("radio", { name: "Config" }));
208
+ expect(screen.getByTestId("config-probe")).toBeTruthy();
209
+ expect(screen.queryByTestId("explorer-probe")).toBeNull();
210
+ // Sidebar heading + the editor area (file tab) both present.
211
+ expect(screen.getByText("Config")).toBeTruthy();
212
+ expect(screen.getByRole("tab", { name: /main\.go/ })).toBeTruthy();
213
+ });
214
+
215
+ it("supports controlled view selection via view/onViewChange", () => {
216
+ const onViewChange = vi.fn();
217
+ renderSurface({
218
+ extraViews: [configView],
219
+ view: "configure",
220
+ onViewChange,
221
+ });
222
+ expect(screen.getByTestId("config-probe")).toBeTruthy();
223
+ fireEvent.click(screen.getByRole("radio", { name: "Search" }));
224
+ expect(onViewChange).toHaveBeenCalledWith("search");
225
+ // Controlled: the view does not change until the owner re-renders.
226
+ expect(screen.getByTestId("config-probe")).toBeTruthy();
227
+ });
228
+
229
+ it("falls back to the explorer when the active view id no longer exists", () => {
230
+ // e.g. a contextual view (Inspect) that disappeared while active.
231
+ renderSurface({ extraViews: [], view: "inspect" });
232
+ expect(screen.getByTestId("explorer-probe")).toBeTruthy();
233
+ expect(
234
+ screen.getByRole("radio", { name: "Explorer" }).getAttribute("aria-checked"),
235
+ ).toBe("true");
236
+ });
237
+
238
+ it("moves across built-in and injected views with arrow keys", () => {
239
+ renderSurface({ extraViews: [configView] });
240
+ const search = screen.getByRole("radio", { name: "Search" });
241
+ fireEvent.click(search);
242
+ fireEvent.keyDown(search, { key: "ArrowDown" });
243
+ expect(screen.getByTestId("config-probe")).toBeTruthy();
244
+ });
245
+ });
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Host-injected virtual documents — editor tabs that are not workspace files
249
+ // (the session's plan.md today). Same open/pin/close semantics as file tabs;
250
+ // only the body rendering diverges.
251
+ // ---------------------------------------------------------------------------
252
+
253
+ const PLAN_ENTRY_ID = virtualEntryId("plan");
254
+
255
+ const planDocument = {
256
+ entryId: PLAN_ENTRY_ID,
257
+ path: "plan.md",
258
+ content: <div data-testid="plan-doc-probe" />,
259
+ };
260
+
261
+ describe("WorkspaceSurface virtualDocuments", () => {
262
+ it("renders the virtual document's content — no FileViewer, no breadcrumbs", () => {
263
+ renderSurface({
264
+ virtualDocuments: [planDocument],
265
+ editors: [{ entryId: PLAN_ENTRY_ID, path: "plan.md", preview: false }],
266
+ selectedFile: { entryId: PLAN_ENTRY_ID, path: "plan.md" },
267
+ });
268
+ expect(screen.getByTestId("plan-doc-probe")).toBeTruthy();
269
+ expect(viewerProps.length).toBe(0);
270
+ expect(screen.queryByRole("navigation", { name: "File location" })).toBeNull();
271
+ });
272
+
273
+ it("shows the virtual document as an ordinary tab (basename label)", () => {
274
+ renderSurface({
275
+ virtualDocuments: [planDocument],
276
+ editors: [
277
+ { entryId: "e1", path: "src/main.go", preview: false },
278
+ { entryId: PLAN_ENTRY_ID, path: "plan.md", preview: false },
279
+ ],
280
+ selectedFile: { entryId: PLAN_ENTRY_ID, path: "plan.md" },
281
+ });
282
+ const tab = screen.getByRole("tab", { name: /plan\.md/ });
283
+ expect(tab.getAttribute("aria-selected")).toBe("true");
284
+ });
285
+
286
+ it("keeps file tabs on the FileViewer path while a virtual document is injected", () => {
287
+ renderSurface({
288
+ virtualDocuments: [planDocument],
289
+ editors: [
290
+ { entryId: "e1", path: "src/main.go", preview: false },
291
+ { entryId: PLAN_ENTRY_ID, path: "plan.md", preview: false },
292
+ ],
293
+ selectedFile: { entryId: "e1", path: "src/main.go" },
294
+ });
295
+ expect(screen.queryByTestId("plan-doc-probe")).toBeNull();
296
+ expect(viewerProps.at(-1)?.selectedFile).toEqual({
297
+ entryId: "e1",
298
+ path: "src/main.go",
299
+ });
300
+ });
301
+
302
+ it("a virtual entry id can never alias a real file of the same path", () => {
303
+ // The NUL-namespaced id is the collision guard: a workspace file named
304
+ // plan.md under a real entry must not hijack the virtual document body.
305
+ renderSurface({
306
+ virtualDocuments: [planDocument],
307
+ editors: [{ entryId: "e1", path: "plan.md", preview: false }],
308
+ selectedFile: { entryId: "e1", path: "plan.md" },
309
+ });
310
+ expect(screen.queryByTestId("plan-doc-probe")).toBeNull();
311
+ expect(viewerProps.at(-1)?.selectedFile).toEqual({
312
+ entryId: "e1",
313
+ path: "plan.md",
314
+ });
315
+ });
316
+
317
+ it("scrolls the document body vertically only — wide content reflows, never a sideways scrollbar", () => {
318
+ renderSurface({
319
+ virtualDocuments: [planDocument],
320
+ editors: [{ entryId: PLAN_ENTRY_ID, path: "plan.md", preview: false }],
321
+ selectedFile: { entryId: PLAN_ENTRY_ID, path: "plan.md" },
322
+ });
323
+ const body = screen.getByTestId("plan-doc-probe").parentElement!;
324
+ expect(body.className).toContain("overflow-y-auto");
325
+ // overflow-y alone computes overflow-x to auto; the explicit hidden +
326
+ // min-w-0 pair is what forces documents to reflow at narrow widths.
327
+ expect(body.className).toContain("overflow-x-hidden");
328
+ expect(body.className).toContain("min-w-0");
329
+ });
330
+ });
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // Workspace entry management in the Explorer sidebar
334
+ // ---------------------------------------------------------------------------
335
+
336
+ describe("WorkspaceSurface entry management", () => {
337
+ it("renders an Add Folder footer action when onAddLocalFolder is provided", () => {
338
+ const onAddLocalFolder = vi.fn();
339
+ renderSurface({ onAddLocalFolder });
340
+ fireEvent.click(screen.getByRole("button", { name: "Add Folder" }));
341
+ expect(onAddLocalFolder).toHaveBeenCalledTimes(1);
342
+ });
343
+
344
+ it("omits the Add Folder action without the callback (web has no native picker)", () => {
345
+ renderSurface();
346
+ expect(screen.queryByRole("button", { name: "Add Folder" })).toBeNull();
347
+ });
348
+ });
@@ -0,0 +1,132 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { create } from "@bufbuild/protobuf";
3
+ import {
4
+ FileChangeSchema,
5
+ type FileChange,
6
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
7
+ import { FileChangeType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
8
+ import { findChangeForSelection } from "../findChangeForSelection";
9
+ import type { WorkspaceEntry } from "../useWorkspaceEntries";
10
+
11
+ function git(id: string, name: string): WorkspaceEntry {
12
+ return { id, name, type: "git", gitUrl: `https://github.com/${name}.git`, gitBranch: "main" };
13
+ }
14
+
15
+ function local(id: string, name: string, localPath: string): WorkspaceEntry {
16
+ return { id, name, type: "local", localPath };
17
+ }
18
+
19
+ function change(
20
+ fields: { path?: string; absolutePath?: string; changeType?: FileChangeType },
21
+ ): FileChange {
22
+ return create(FileChangeSchema, {
23
+ path: fields.path ?? "",
24
+ absolutePath: fields.absolutePath ?? "",
25
+ changeType: fields.changeType ?? FileChangeType.MODIFY,
26
+ });
27
+ }
28
+
29
+ const DAYTONA = "/home/daytona/workspace";
30
+
31
+ describe("findChangeForSelection", () => {
32
+ it("matches a single-entry change by exact repo-relative path", () => {
33
+ const entries = [git("e1", "org/repo")];
34
+ const changes = [change({ path: "src/main.go" })];
35
+ expect(
36
+ findChangeForSelection({ entryId: "e1", path: "src/main.go" }, changes, entries),
37
+ ).toBe(changes[0]);
38
+ });
39
+
40
+ it("returns null when no change touches the open file", () => {
41
+ const entries = [git("e1", "org/repo")];
42
+ const changes = [change({ path: "src/other.go" })];
43
+ expect(
44
+ findChangeForSelection({ entryId: "e1", path: "src/main.go" }, changes, entries),
45
+ ).toBeNull();
46
+ });
47
+
48
+ it("returns null for an empty change list", () => {
49
+ const entries = [git("e1", "org/repo")];
50
+ expect(
51
+ findChangeForSelection({ entryId: "e1", path: "src/main.go" }, [], entries),
52
+ ).toBeNull();
53
+ });
54
+
55
+ it("does NOT collide across repos with the same basename (entry-correct)", () => {
56
+ // The crux: a suffix match would attach repoA's change to repoB's file.
57
+ // Entry-anchored resolution must keep them apart.
58
+ const entries = [git("a", "org/repoA"), git("b", "org/repoB")];
59
+ const changes = [
60
+ change({ path: "org/repoA/src/a.ts" }),
61
+ change({ path: "org/repoB/src/a.ts" }),
62
+ ];
63
+
64
+ expect(
65
+ findChangeForSelection({ entryId: "a", path: "src/a.ts" }, changes, entries),
66
+ ).toBe(changes[0]);
67
+ expect(
68
+ findChangeForSelection({ entryId: "b", path: "src/a.ts" }, changes, entries),
69
+ ).toBe(changes[1]);
70
+ });
71
+
72
+ it("strips the subdir prefix for a multi-git session", () => {
73
+ const entries = [git("a", "org/repoA"), git("b", "org/repoB")];
74
+ const changes = [change({ path: "org/repoB/lib/util.ts" })];
75
+ expect(
76
+ findChangeForSelection({ entryId: "b", path: "lib/util.ts" }, changes, entries),
77
+ ).toBe(changes[0]);
78
+ });
79
+
80
+ it("reduces a cloud-absolute change path via the sandbox root", () => {
81
+ const entries = [git("e1", "org/repo")];
82
+ const changes = [change({ path: `${DAYTONA}/src/main.go` })];
83
+ expect(
84
+ findChangeForSelection(
85
+ { entryId: "e1", path: "src/main.go" },
86
+ changes,
87
+ entries,
88
+ DAYTONA,
89
+ ),
90
+ ).toBe(changes[0]);
91
+ });
92
+
93
+ it("falls back to absolutePath when path is empty", () => {
94
+ const entries = [local("e1", "app", "/Users/dev/app")];
95
+ const changes = [change({ path: "", absolutePath: "/Users/dev/app/src/main.go" })];
96
+ expect(
97
+ findChangeForSelection({ entryId: "e1", path: "src/main.go" }, changes, entries),
98
+ ).toBe(changes[0]);
99
+ });
100
+
101
+ it("matches a DELETE change (viewer decides diff-only, not this resolver)", () => {
102
+ const entries = [git("e1", "org/repo")];
103
+ const changes = [change({ path: "src/gone.ts", changeType: FileChangeType.DELETE })];
104
+ const found = findChangeForSelection(
105
+ { entryId: "e1", path: "src/gone.ts" },
106
+ changes,
107
+ entries,
108
+ );
109
+ expect(found).toBe(changes[0]);
110
+ expect(found?.changeType).toBe(FileChangeType.DELETE);
111
+ });
112
+
113
+ it("degrades to null for an ambiguous multi-entry change path", () => {
114
+ const entries = [git("a", "org/repoA"), git("b", "org/repoB")];
115
+ // No subdir prefix and no sandbox root → the resolver refuses to guess.
116
+ const changes = [change({ path: "src/a.ts" })];
117
+ expect(
118
+ findChangeForSelection({ entryId: "a", path: "src/a.ts" }, changes, entries),
119
+ ).toBeNull();
120
+ });
121
+
122
+ it("returns the first matching change (net-per-file lists have one per path)", () => {
123
+ const entries = [git("e1", "org/repo")];
124
+ const changes = [
125
+ change({ path: "src/a.ts" }),
126
+ change({ path: "src/b.ts" }),
127
+ ];
128
+ expect(
129
+ findChangeForSelection({ entryId: "e1", path: "src/b.ts" }, changes, entries),
130
+ ).toBe(changes[1]);
131
+ });
132
+ });
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { findHighlightRanges } from "../matchContentPreview";
3
+
4
+ describe("findHighlightRanges", () => {
5
+ it("returns no ranges for an empty or whitespace query", () => {
6
+ expect(findHighlightRanges("hello world", "")).toEqual([]);
7
+ expect(findHighlightRanges("hello world", " ")).toEqual([]);
8
+ });
9
+
10
+ it("returns no ranges when the query is absent", () => {
11
+ expect(findHighlightRanges("hello world", "xyz")).toEqual([]);
12
+ });
13
+
14
+ it("finds a single occurrence", () => {
15
+ expect(findHighlightRanges("hello world", "world")).toEqual([
16
+ { start: 6, end: 11 },
17
+ ]);
18
+ });
19
+
20
+ it("finds every occurrence on the line", () => {
21
+ expect(findHighlightRanges("foo bar foo baz foo", "foo")).toEqual([
22
+ { start: 0, end: 3 },
23
+ { start: 8, end: 11 },
24
+ { start: 16, end: 19 },
25
+ ]);
26
+ });
27
+
28
+ it("matches case-insensitively but reports offsets into the original text", () => {
29
+ expect(findHighlightRanges("TODO todo ToDo", "todo")).toEqual([
30
+ { start: 0, end: 4 },
31
+ { start: 5, end: 9 },
32
+ { start: 10, end: 14 },
33
+ ]);
34
+ });
35
+
36
+ it("does not produce overlapping ranges (advances past each match)", () => {
37
+ // "aa" in "aaaa" → [0,2) and [2,4), not [1,3).
38
+ expect(findHighlightRanges("aaaa", "aa")).toEqual([
39
+ { start: 0, end: 2 },
40
+ { start: 2, end: 4 },
41
+ ]);
42
+ });
43
+
44
+ it("trims the query before matching", () => {
45
+ expect(findHighlightRanges("hello world", " world ")).toEqual([
46
+ { start: 6, end: 11 },
47
+ ]);
48
+ });
49
+
50
+ it("handles multibyte text with correct code-unit offsets", () => {
51
+ const text = "日本 needle 世界";
52
+ const [range] = findHighlightRanges(text, "needle");
53
+ expect(text.slice(range.start, range.end)).toBe("needle");
54
+ });
55
+ });
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { matchWorkspaceFiles } from "../matchWorkspaceFiles";
3
+ import type { WorkspaceFileEntry } from "../WorkspaceFileLister";
4
+
5
+ function file(path: string): WorkspaceFileEntry {
6
+ return { path, isDirectory: false };
7
+ }
8
+
9
+ describe("matchWorkspaceFiles", () => {
10
+ it("returns [] for an empty or whitespace query", () => {
11
+ const files = [file("src/index.ts")];
12
+ expect(matchWorkspaceFiles(files, "")).toEqual([]);
13
+ expect(matchWorkspaceFiles(files, " ")).toEqual([]);
14
+ });
15
+
16
+ it("matches case-insensitively on the full relative path", () => {
17
+ const files = [file("src/Components/Button.tsx")];
18
+ const result = matchWorkspaceFiles(files, "button");
19
+ expect(result).toHaveLength(1);
20
+ expect(result[0].path).toBe("src/Components/Button.tsx");
21
+ });
22
+
23
+ it("finds files by a cross-segment path substring", () => {
24
+ const files = [file("src/utils/helper.ts"), file("README.md")];
25
+ const result = matchWorkspaceFiles(files, "utils/helper");
26
+ expect(result.map((m) => m.path)).toEqual(["src/utils/helper.ts"]);
27
+ });
28
+
29
+ it("excludes directories", () => {
30
+ const files: WorkspaceFileEntry[] = [
31
+ { path: "src", isDirectory: true },
32
+ { path: "src/index.ts", isDirectory: false },
33
+ ];
34
+ const result = matchWorkspaceFiles(files, "src");
35
+ expect(result.map((m) => m.path)).toEqual(["src/index.ts"]);
36
+ });
37
+
38
+ it("excludes advisory notice entries even if the query matches the message", () => {
39
+ const files: WorkspaceFileEntry[] = [
40
+ { path: "src/tree.ts", isDirectory: false },
41
+ { path: "... (tree truncated by GitHub — repository has too many files)", isDirectory: false, notice: true },
42
+ ];
43
+ const result = matchWorkspaceFiles(files, "tree");
44
+ expect(result.map((m) => m.path)).toEqual(["src/tree.ts"]);
45
+ });
46
+
47
+ it("returns [] when nothing matches", () => {
48
+ expect(matchWorkspaceFiles([file("src/index.ts")], "zzz")).toEqual([]);
49
+ });
50
+
51
+ it("reports the contiguous highlight range as code-unit offsets", () => {
52
+ const [m] = matchWorkspaceFiles([file("src/index.ts")], "index");
53
+ expect(m.matchStart).toBe(4);
54
+ expect(m.matchEnd).toBe(9);
55
+ expect("src/index.ts".slice(m.matchStart, m.matchEnd)).toBe("index");
56
+ });
57
+
58
+ it("ranks a basename hit above a path-only hit", () => {
59
+ const files = [
60
+ file("app/main.ts"), // "app" is only in the dir segment (path-only)
61
+ file("src/app.ts"), // "app" is the basename
62
+ ];
63
+ const result = matchWorkspaceFiles(files, "app");
64
+ expect(result[0].path).toBe("src/app.ts");
65
+ expect(result[1].path).toBe("app/main.ts");
66
+ });
67
+
68
+ it("ranks an earlier match offset first among basename hits", () => {
69
+ const files = [file("src/index.ts"), file("index.ts")];
70
+ const result = matchWorkspaceFiles(files, "index");
71
+ // "index.ts" matches at offset 0; "src/index.ts" at offset 4.
72
+ expect(result.map((m) => m.path)).toEqual(["index.ts", "src/index.ts"]);
73
+ });
74
+
75
+ it("breaks ties by shorter path then lexicographically", () => {
76
+ const files = [
77
+ file("b/thing.ts"),
78
+ file("a/thing.ts"),
79
+ file("thing.ts"),
80
+ ];
81
+ const result = matchWorkspaceFiles(files, "thing");
82
+ // "thing.ts" (offset 0) first; the two offset-2 hits tie on length → lexicographic.
83
+ expect(result.map((m) => m.path)).toEqual([
84
+ "thing.ts",
85
+ "a/thing.ts",
86
+ "b/thing.ts",
87
+ ]);
88
+ });
89
+
90
+ it("handles unicode paths and slices the range consistently", () => {
91
+ const files = [file("src/café/résumé.ts")];
92
+ const [m] = matchWorkspaceFiles(files, "résumé");
93
+ expect(m).toBeDefined();
94
+ expect("src/café/résumé.ts".slice(m.matchStart, m.matchEnd)).toBe("résumé");
95
+ });
96
+
97
+ it("is deterministic regardless of input order", () => {
98
+ const a = [file("z.ts"), file("a.ts"), file("m.ts")];
99
+ const b = [file("a.ts"), file("m.ts"), file("z.ts")];
100
+ const qa = matchWorkspaceFiles(a, ".ts").map((m) => m.path);
101
+ const qb = matchWorkspaceFiles(b, ".ts").map((m) => m.path);
102
+ expect(qa).toEqual(qb);
103
+ });
104
+ });