@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,86 @@
1
+ import { describe, it, expect, vi, afterEach } from "vitest";
2
+ import { render, screen, fireEvent, cleanup } from "@testing-library/react";
3
+ import { FileTreeNode } from "../FileTreeNode";
4
+ import type { TreeNode } from "../tree-node";
5
+
6
+ afterEach(cleanup);
7
+
8
+ const fileNode: TreeNode = { name: "a.ts", path: "src/a.ts" };
9
+ const folderNode: TreeNode = {
10
+ name: "src",
11
+ path: "src/",
12
+ children: [fileNode],
13
+ };
14
+
15
+ describe("FileTreeNode — explorer props", () => {
16
+ it("renders an icon before the name when showIcons is set", () => {
17
+ render(
18
+ <ul>
19
+ <FileTreeNode node={fileNode} selectedPath="" onSelect={vi.fn()} depth={0} showIcons />
20
+ </ul>,
21
+ );
22
+ const button = screen.getByText("a.ts").closest("button")!;
23
+ expect(button.querySelector("svg")).toBeTruthy();
24
+ });
25
+
26
+ it("renders no icon by default (unchanged for existing consumers)", () => {
27
+ render(
28
+ <ul>
29
+ <FileTreeNode node={fileNode} selectedPath="" onSelect={vi.fn()} depth={0} />
30
+ </ul>,
31
+ );
32
+ const button = screen.getByText("a.ts").closest("button")!;
33
+ expect(button.querySelector("svg")).toBeNull();
34
+ });
35
+
36
+ it("calls onActivate on double-click of a file", () => {
37
+ const onActivate = vi.fn();
38
+ render(
39
+ <ul>
40
+ <FileTreeNode
41
+ node={fileNode}
42
+ selectedPath=""
43
+ onSelect={vi.fn()}
44
+ onActivate={onActivate}
45
+ depth={0}
46
+ />
47
+ </ul>,
48
+ );
49
+ fireEvent.doubleClick(screen.getByText("a.ts"));
50
+ expect(onActivate).toHaveBeenCalledWith("src/a.ts");
51
+ });
52
+
53
+ it("does not call onActivate on a folder double-click", () => {
54
+ const onActivate = vi.fn();
55
+ render(
56
+ <ul>
57
+ <FileTreeNode
58
+ node={folderNode}
59
+ selectedPath=""
60
+ onSelect={vi.fn()}
61
+ onActivate={onActivate}
62
+ depth={0}
63
+ />
64
+ </ul>,
65
+ );
66
+ fireEvent.doubleClick(screen.getByText("src"));
67
+ expect(onActivate).not.toHaveBeenCalled();
68
+ });
69
+
70
+ it("draws an indent guide on nested groups when indentGuides is set", () => {
71
+ const { container } = render(
72
+ <ul>
73
+ <FileTreeNode
74
+ node={folderNode}
75
+ selectedPath=""
76
+ onSelect={vi.fn()}
77
+ depth={0}
78
+ indentGuides
79
+ />
80
+ </ul>,
81
+ );
82
+ // The nested group carries the guide border class.
83
+ const group = container.querySelector('ul[role="group"]');
84
+ expect(group?.className).toContain("border-l");
85
+ });
86
+ });
@@ -43,6 +43,14 @@ export function stripFrontmatter(content: string): string {
43
43
  const ENCLOSING_MARKDOWN_FENCE_RE =
44
44
  /^(`{3,})[ \t]*(?:markdown|md)[ \t]*\r?\n([\s\S]*?)\r?\n\1[ \t]*$/i;
45
45
 
46
+ /**
47
+ * Matches content whose ENTIRE body is a single fenced code block with an
48
+ * EMPTY info string (no language tag). Only meaningful for surfaces that know
49
+ * the body is markdown by contract (a Plan-mode plan) — see
50
+ * {@link unwrapEnclosingMarkdownFence}'s `allowBareFence`.
51
+ */
52
+ const ENCLOSING_BARE_FENCE_RE = /^(`{3,})[ \t]*\r?\n([\s\S]*?)\r?\n\1[ \t]*$/;
53
+
46
54
  /**
47
55
  * Unwraps a message the model wrapped entirely in a ```markdown / ```md fence.
48
56
  *
@@ -53,6 +61,14 @@ const ENCLOSING_MARKDOWN_FENCE_RE =
53
61
  * no-op for everything else (already-rich markdown, prose, or a reply that is
54
62
  * legitimately a single code block).
55
63
  *
64
+ * `allowBareFence` extends the unwrap to a whole-body fence with NO language
65
+ * tag. For an ordinary chat message a bare fence is ambiguous (it may be a
66
+ * legitimate single code block), so the default stays strict. A Plan-mode plan
67
+ * is different by contract: the turn's output IS a markdown document, so a
68
+ * reply that is one giant untagged fence is a wrapped plan, not code. Only
69
+ * plan-document surfaces pass `true`. A fence tagged with any other language
70
+ * (```python …) is never unwrapped in either mode.
71
+ *
56
72
  * Render-time only: callers pass it the text right before handing it to the
57
73
  * markdown renderer, so the transcript and the raw artifact stay faithful to
58
74
  * what the agent produced — a single source of truth for the unwrap, with no
@@ -60,9 +76,47 @@ const ENCLOSING_MARKDOWN_FENCE_RE =
60
76
  * arrived yet, so this no-ops and the live text renders as typed; it unwraps
61
77
  * once the block closes.
62
78
  */
63
- export function unwrapEnclosingMarkdownFence(content: string): string {
64
- const match = ENCLOSING_MARKDOWN_FENCE_RE.exec(content.trim());
65
- return match ? match[2] : content;
79
+ export function unwrapEnclosingMarkdownFence(
80
+ content: string,
81
+ allowBareFence = false,
82
+ ): string {
83
+ const trimmed = content.trim();
84
+ const tagged = ENCLOSING_MARKDOWN_FENCE_RE.exec(trimmed);
85
+ if (tagged) return tagged[2];
86
+ if (allowBareFence) {
87
+ const bare = ENCLOSING_BARE_FENCE_RE.exec(trimmed);
88
+ if (bare) return bare[2];
89
+ }
90
+ return content;
91
+ }
92
+
93
+ /**
94
+ * Matches a document that OPENS with an `# H1` heading (optionally preceded by
95
+ * blank lines only). Group 1 is the heading text; the match spans through the
96
+ * heading's trailing newline(s) so `body` starts at the first content line.
97
+ */
98
+ const LEADING_H1_RE = /^#[ \t]+(.+?)[ \t]*(?:\r?\n+|$)/;
99
+
100
+ /**
101
+ * Splits a markdown document into its leading `# H1` title and the remaining
102
+ * body, for surfaces that render the title in their own chrome (the plan
103
+ * document header) instead of inside the prose flow.
104
+ *
105
+ * Only a heading at the very start of the document counts as the title — an
106
+ * `# H1` further down is document content and stays in the body. When there is
107
+ * no leading H1, `title` is `null` and `body` is the input unchanged.
108
+ *
109
+ * Render-time only, like {@link unwrapEnclosingMarkdownFence}: the stored
110
+ * message/artifact keeps its heading; only the presentation moves it.
111
+ */
112
+ export function extractLeadingH1(markdown: string): {
113
+ readonly title: string | null;
114
+ readonly body: string;
115
+ } {
116
+ const trimmed = markdown.trim();
117
+ const match = LEADING_H1_RE.exec(trimmed);
118
+ if (!match) return { title: null, body: markdown };
119
+ return { title: match[1], body: trimmed.slice(match[0].length) };
66
120
  }
67
121
 
68
122
  /**
@@ -77,7 +131,7 @@ export function unwrapEnclosingMarkdownFence(content: string): string {
77
131
  export const MARKDOWN_COMPONENTS: Components = {
78
132
  p({ children, ...props }: MdProps<"p">) {
79
133
  return (
80
- <p className="text-sm text-foreground mb-3 last:mb-0 leading-relaxed" {...props}>
134
+ <p className="text-sm text-foreground mb-3 last:mb-0 leading-relaxed break-words" {...props}>
81
135
  {children}
82
136
  </p>
83
137
  );
@@ -87,7 +141,7 @@ export const MARKDOWN_COMPONENTS: Components = {
87
141
  return (
88
142
  <a
89
143
  href={href}
90
- className="text-primary underline underline-offset-2 hover:text-primary-muted"
144
+ className="text-primary underline underline-offset-2 hover:text-primary-muted break-words"
91
145
  target="_blank"
92
146
  rel="noopener noreferrer"
93
147
  {...props}
@@ -99,7 +153,7 @@ export const MARKDOWN_COMPONENTS: Components = {
99
153
 
100
154
  h1({ children, ...props }: MdProps<"h1">) {
101
155
  return (
102
- <h1 className="text-lg font-semibold text-foreground mt-5 mb-2 first:mt-0" {...props}>
156
+ <h1 className="text-lg font-semibold text-foreground mt-5 mb-2 first:mt-0 break-words" {...props}>
103
157
  {children}
104
158
  </h1>
105
159
  );
@@ -107,7 +161,7 @@ export const MARKDOWN_COMPONENTS: Components = {
107
161
 
108
162
  h2({ children, ...props }: MdProps<"h2">) {
109
163
  return (
110
- <h2 className="text-base font-semibold text-foreground mt-5 mb-2 first:mt-0" {...props}>
164
+ <h2 className="text-base font-semibold text-foreground mt-5 mb-2 first:mt-0 break-words" {...props}>
111
165
  {children}
112
166
  </h2>
113
167
  );
@@ -115,7 +169,7 @@ export const MARKDOWN_COMPONENTS: Components = {
115
169
 
116
170
  h3({ children, ...props }: MdProps<"h3">) {
117
171
  return (
118
- <h3 className="text-sm font-semibold text-foreground mt-4 mb-1.5 first:mt-0" {...props}>
172
+ <h3 className="text-sm font-semibold text-foreground mt-4 mb-1.5 first:mt-0 break-words" {...props}>
119
173
  {children}
120
174
  </h3>
121
175
  );
@@ -123,7 +177,7 @@ export const MARKDOWN_COMPONENTS: Components = {
123
177
 
124
178
  h4({ children, ...props }: MdProps<"h4">) {
125
179
  return (
126
- <h4 className="text-sm font-medium text-foreground mt-3 mb-1 first:mt-0" {...props}>
180
+ <h4 className="text-sm font-medium text-foreground mt-3 mb-1 first:mt-0 break-words" {...props}>
127
181
  {children}
128
182
  </h4>
129
183
  );
@@ -147,7 +201,7 @@ export const MARKDOWN_COMPONENTS: Components = {
147
201
 
148
202
  li({ children, ...props }: MdProps<"li">) {
149
203
  return (
150
- <li className="leading-relaxed" {...props}>
204
+ <li className="leading-relaxed break-words" {...props}>
151
205
  {children}
152
206
  </li>
153
207
  );
@@ -195,7 +249,7 @@ export const MARKDOWN_COMPONENTS: Components = {
195
249
 
196
250
  return (
197
251
  <code
198
- className="font-mono text-xs bg-muted px-1 py-0.5 rounded text-foreground"
252
+ className="font-mono text-xs bg-muted px-1 py-0.5 rounded text-foreground break-words"
199
253
  {...props}
200
254
  >
201
255
  {children}
@@ -263,3 +317,54 @@ export const MARKDOWN_COMPONENTS: Components = {
263
317
  );
264
318
  },
265
319
  };
320
+
321
+ /**
322
+ * Document-grade typography for a Plan-mode plan: one step up the heading
323
+ * scale from the chat-tuned {@link MARKDOWN_COMPONENTS}, wider section
324
+ * spacing, and a hairline under `##` section headings for scannability. The
325
+ * plan is a reviewable document, not a chat bubble — its headings must carry
326
+ * the structure at a glance.
327
+ *
328
+ * Everything except the headings is inherited from `MARKDOWN_COMPONENTS`, so
329
+ * body text, code, tables, and links render identically across chat and plan
330
+ * surfaces. Used by `PlanDocumentMessage` (thread) — the same treatment any
331
+ * plan-document surface should share.
332
+ */
333
+ export const PLAN_DOCUMENT_MARKDOWN_COMPONENTS: Components = {
334
+ ...MARKDOWN_COMPONENTS,
335
+
336
+ h1({ children, ...props }: MdProps<"h1">) {
337
+ return (
338
+ <h1 className="text-lg font-semibold text-foreground mt-6 mb-3 first:mt-0 break-words" {...props}>
339
+ {children}
340
+ </h1>
341
+ );
342
+ },
343
+
344
+ h2({ children, ...props }: MdProps<"h2">) {
345
+ return (
346
+ <h2
347
+ className="text-base font-semibold text-foreground mt-6 mb-2.5 pb-1 border-b border-border-muted first:mt-0 break-words"
348
+ {...props}
349
+ >
350
+ {children}
351
+ </h2>
352
+ );
353
+ },
354
+
355
+ h3({ children, ...props }: MdProps<"h3">) {
356
+ return (
357
+ <h3 className="text-sm font-semibold text-foreground mt-5 mb-2 first:mt-0 break-words" {...props}>
358
+ {children}
359
+ </h3>
360
+ );
361
+ },
362
+
363
+ h4({ children, ...props }: MdProps<"h4">) {
364
+ return (
365
+ <h4 className="text-sm font-medium text-foreground mt-4 mb-1.5 first:mt-0 break-words" {...props}>
366
+ {children}
367
+ </h4>
368
+ );
369
+ },
370
+ };
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * Utility for respecting the user's motion preference (prefers-reduced-motion)
3
- * in programmatic viewport animations (fitView, setCenter, etc.) that are NOT
4
- * covered by the CSS-level reduced-motion rule in styles.css.
3
+ * in programmatic viewport animations (fitView, setCenter, scrollIntoView, etc.)
4
+ * that are NOT covered by the CSS-level reduced-motion rule in styles.css.
5
5
  *
6
- * React Flow's JS-driven animations (duration parameter on fitView/setCenter)
7
- * bypass CSS media queries — they must be explicitly checked.
6
+ * JS-driven animations (React Flow's duration parameter on fitView/setCenter,
7
+ * smooth `scrollIntoView`) bypass CSS media queries — they must be explicitly
8
+ * checked. Lives in `internal/` because it is cross-cutting: the workflow graph
9
+ * and the workspace file viewer both depend on it, and `internal/` may not
10
+ * depend on a feature domain.
8
11
  */
9
12
 
10
13
  let cachedPreference: boolean | null = null;
@@ -0,0 +1,216 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { WorkspaceEditorsStore, editorKey } from "../workspace-editors-store";
3
+
4
+ function paths(store: WorkspaceEditorsStore): string[] {
5
+ return store.getSnapshot().editors.map((e) => e.path);
6
+ }
7
+
8
+ let store: WorkspaceEditorsStore;
9
+ beforeEach(() => {
10
+ store = new WorkspaceEditorsStore();
11
+ });
12
+
13
+ describe("WorkspaceEditorsStore", () => {
14
+ it("starts empty", () => {
15
+ const snap = store.getSnapshot();
16
+ expect(snap.editors).toEqual([]);
17
+ expect(snap.activeKey).toBeNull();
18
+ expect(snap.activeFile).toBeNull();
19
+ });
20
+
21
+ it("openPreview adds a preview tab and makes it active", () => {
22
+ store.openPreview("e1", "a.ts");
23
+ const snap = store.getSnapshot();
24
+ expect(snap.editors).toEqual([{ entryId: "e1", path: "a.ts", preview: true }]);
25
+ expect(snap.activeFile).toEqual({ entryId: "e1", path: "a.ts" });
26
+ });
27
+
28
+ it("openPreview reuses the single preview slot for a new file", () => {
29
+ store.openPreview("e1", "a.ts");
30
+ store.openPreview("e1", "b.ts");
31
+ // b.ts replaced a.ts in place — still one tab, now b.ts.
32
+ expect(paths(store)).toEqual(["b.ts"]);
33
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e1", path: "b.ts" });
34
+ });
35
+
36
+ it("openPreview on an already-open editor just focuses it (no duplicate)", () => {
37
+ store.openPinned("e1", "a.ts");
38
+ store.openPinned("e1", "b.ts");
39
+ store.openPreview("e1", "a.ts");
40
+ expect(paths(store)).toEqual(["a.ts", "b.ts"]);
41
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e1", path: "a.ts" });
42
+ // a.ts stays pinned — focusing does not turn it back into a preview.
43
+ expect(store.getSnapshot().editors[0].preview).toBe(false);
44
+ });
45
+
46
+ it("a pinned tab survives when a subsequent preview opens", () => {
47
+ store.openPinned("e1", "a.ts");
48
+ store.openPreview("e1", "b.ts");
49
+ store.openPreview("e1", "c.ts");
50
+ // a.ts pinned stays; the preview slot cycled b.ts -> c.ts.
51
+ expect(paths(store)).toEqual(["a.ts", "c.ts"]);
52
+ });
53
+
54
+ it("openPinned promotes an existing preview tab to persistent", () => {
55
+ store.openPreview("e1", "a.ts");
56
+ expect(store.getSnapshot().editors[0].preview).toBe(true);
57
+ store.openPinned("e1", "a.ts");
58
+ expect(store.getSnapshot().editors[0].preview).toBe(false);
59
+ expect(paths(store)).toEqual(["a.ts"]);
60
+ });
61
+
62
+ it("pin clears the preview flag", () => {
63
+ store.openPreview("e1", "a.ts");
64
+ store.pin("e1", "a.ts");
65
+ expect(store.getSnapshot().editors[0].preview).toBe(false);
66
+ });
67
+
68
+ it("activate focuses an open tab and no-ops for an unopened one", () => {
69
+ store.openPinned("e1", "a.ts");
70
+ store.openPinned("e1", "b.ts");
71
+ store.activate("e1", "a.ts");
72
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e1", path: "a.ts" });
73
+ store.activate("e1", "missing.ts");
74
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e1", path: "a.ts" });
75
+ });
76
+
77
+ it("close removes a tab and activates the right neighbor", () => {
78
+ store.openPinned("e1", "a.ts");
79
+ store.openPinned("e1", "b.ts");
80
+ store.openPinned("e1", "c.ts");
81
+ store.activate("e1", "b.ts");
82
+ store.close("e1", "b.ts");
83
+ expect(paths(store)).toEqual(["a.ts", "c.ts"]);
84
+ // The tab that shifted into b's slot (c.ts) becomes active.
85
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e1", path: "c.ts" });
86
+ });
87
+
88
+ it("close activates the left neighbor when the last tab is closed", () => {
89
+ store.openPinned("e1", "a.ts");
90
+ store.openPinned("e1", "b.ts");
91
+ store.activate("e1", "b.ts");
92
+ store.close("e1", "b.ts");
93
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e1", path: "a.ts" });
94
+ });
95
+
96
+ it("closing the only tab empties the group", () => {
97
+ store.openPreview("e1", "a.ts");
98
+ store.close("e1", "a.ts");
99
+ expect(store.getSnapshot().editors).toEqual([]);
100
+ expect(store.getSnapshot().activeFile).toBeNull();
101
+ });
102
+
103
+ it("closing an inactive tab keeps the active one", () => {
104
+ store.openPinned("e1", "a.ts");
105
+ store.openPinned("e1", "b.ts");
106
+ store.activate("e1", "b.ts");
107
+ store.close("e1", "a.ts");
108
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e1", path: "b.ts" });
109
+ });
110
+
111
+ it("keys files by entry, so same path in two entries are distinct tabs", () => {
112
+ store.openPinned("e1", "a.ts");
113
+ store.openPinned("e2", "a.ts");
114
+ expect(store.getSnapshot().editors).toHaveLength(2);
115
+ expect(store.getSnapshot().activeFile).toEqual({ entryId: "e2", path: "a.ts" });
116
+ });
117
+
118
+ it("returns a stable snapshot ref between mutations", () => {
119
+ store.openPreview("e1", "a.ts");
120
+ const snapA = store.getSnapshot();
121
+ const snapB = store.getSnapshot();
122
+ expect(snapA).toBe(snapB);
123
+ store.openPreview("e1", "b.ts");
124
+ expect(store.getSnapshot()).not.toBe(snapA);
125
+ });
126
+
127
+ it("notifies subscribers on mutation", () => {
128
+ let count = 0;
129
+ const unsub = store.subscribe(() => {
130
+ count++;
131
+ });
132
+ store.openPreview("e1", "a.ts");
133
+ expect(count).toBe(1);
134
+ unsub();
135
+ store.openPreview("e1", "b.ts");
136
+ expect(count).toBe(1);
137
+ });
138
+
139
+ it("closeAll empties a populated group and is a no-op when empty", () => {
140
+ store.openPinned("e1", "a.ts");
141
+ let count = 0;
142
+ store.subscribe(() => {
143
+ count++;
144
+ });
145
+ store.closeAll();
146
+ expect(store.getSnapshot().editors).toEqual([]);
147
+ expect(count).toBe(1);
148
+ store.closeAll();
149
+ expect(count).toBe(1);
150
+ });
151
+
152
+ describe("reveal (jump-to-line)", () => {
153
+ it("starts with no reveal, and an open without a line sets none", () => {
154
+ expect(store.getSnapshot().reveal).toBeNull();
155
+ store.openPreview("e1", "a.ts");
156
+ expect(store.getSnapshot().reveal).toBeNull();
157
+ });
158
+
159
+ it("openPreview with a line sets a reveal targeting the active editor", () => {
160
+ store.openPreview("e1", "a.ts", { line: 42 });
161
+ const snap = store.getSnapshot();
162
+ expect(snap.reveal).toEqual({
163
+ key: editorKey("e1", "a.ts"),
164
+ line: 42,
165
+ nonce: expect.any(Number),
166
+ });
167
+ // The reveal always names the now-active editor.
168
+ expect(snap.reveal!.key).toBe(snap.activeKey);
169
+ });
170
+
171
+ it("bumps the nonce on every open-with-line, even for the same file+line", () => {
172
+ store.openPreview("e1", "a.ts", { line: 10 });
173
+ const first = store.getSnapshot().reveal!.nonce;
174
+ // A second search hit in the already-open, already-active file must still
175
+ // re-scroll — so the nonce advances.
176
+ store.openPreview("e1", "a.ts", { line: 10 });
177
+ expect(store.getSnapshot().reveal!.nonce).toBeGreaterThan(first);
178
+ });
179
+
180
+ it("clears the reveal when switching to another tab", () => {
181
+ store.openPinned("e1", "a.ts");
182
+ store.openPreview("e1", "b.ts", { line: 5 });
183
+ expect(store.getSnapshot().reveal).not.toBeNull();
184
+ store.activate("e1", "a.ts");
185
+ expect(store.getSnapshot().reveal).toBeNull();
186
+ });
187
+
188
+ it("clears the reveal on close and closeAll", () => {
189
+ store.openPreview("e1", "a.ts", { line: 5 });
190
+ store.close("e1", "a.ts");
191
+ expect(store.getSnapshot().reveal).toBeNull();
192
+
193
+ store.openPreview("e1", "b.ts", { line: 3 });
194
+ store.closeAll();
195
+ expect(store.getSnapshot().reveal).toBeNull();
196
+ });
197
+
198
+ it("preserves editors and activeFile refs on a reveal-only update", () => {
199
+ store.openPinned("e1", "a.ts");
200
+ const before = store.getSnapshot();
201
+ // Already open + active → only the reveal changes; the tab list and the
202
+ // active-file projection must keep their references (invariant #3).
203
+ store.openPreview("e1", "a.ts", { line: 9 });
204
+ const after = store.getSnapshot();
205
+ expect(after.editors).toBe(before.editors);
206
+ expect(after.activeFile).toBe(before.activeFile);
207
+ expect(after.reveal).toEqual({
208
+ key: editorKey("e1", "a.ts"),
209
+ line: 9,
210
+ nonce: expect.any(Number),
211
+ });
212
+ // Focusing-for-reveal must not turn a pinned tab back into a preview.
213
+ expect(after.editors[0].preview).toBe(false);
214
+ });
215
+ });
216
+ });
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import {
3
+ WorkspaceFileSelectionStore,
4
+ type SelectedWorkspaceFile,
5
+ } from "../workspace-file-selection-store";
6
+
7
+ function file(entryId: string, path: string): SelectedWorkspaceFile {
8
+ return { entryId, path };
9
+ }
10
+
11
+ describe("WorkspaceFileSelectionStore", () => {
12
+ it("starts with null selection", () => {
13
+ const store = new WorkspaceFileSelectionStore();
14
+ expect(store.getSelection()).toBeNull();
15
+ });
16
+
17
+ it("select sets the selection and notifies listeners", () => {
18
+ const store = new WorkspaceFileSelectionStore();
19
+ const listener = vi.fn();
20
+ store.subscribe(listener);
21
+
22
+ store.select(file("ws-1", "src/index.ts"));
23
+ expect(store.getSelection()).toEqual(file("ws-1", "src/index.ts"));
24
+ expect(listener).toHaveBeenCalledOnce();
25
+ });
26
+
27
+ it("select with an equal file does not notify", () => {
28
+ const store = new WorkspaceFileSelectionStore();
29
+ store.select(file("ws-1", "src/index.ts"));
30
+
31
+ const listener = vi.fn();
32
+ store.subscribe(listener);
33
+
34
+ store.select(file("ws-1", "src/index.ts"));
35
+ expect(listener).not.toHaveBeenCalled();
36
+ });
37
+
38
+ it("keeps a stable snapshot reference between mutations (getSnapshot safety)", () => {
39
+ const store = new WorkspaceFileSelectionStore();
40
+ store.select(file("ws-1", "a.ts"));
41
+ expect(store.getSelection()).toBe(store.getSelection());
42
+ });
43
+
44
+ it("select notifies when only the path differs within the same entry", () => {
45
+ const store = new WorkspaceFileSelectionStore();
46
+ store.select(file("ws-1", "a.ts"));
47
+
48
+ const listener = vi.fn();
49
+ store.subscribe(listener);
50
+
51
+ store.select(file("ws-1", "b.ts"));
52
+ expect(store.getSelection()).toEqual(file("ws-1", "b.ts"));
53
+ expect(listener).toHaveBeenCalledOnce();
54
+ });
55
+
56
+ it("deselect clears the selection and notifies", () => {
57
+ const store = new WorkspaceFileSelectionStore();
58
+ store.select(file("ws-1", "a.ts"));
59
+
60
+ const listener = vi.fn();
61
+ store.subscribe(listener);
62
+
63
+ store.deselect();
64
+ expect(store.getSelection()).toBeNull();
65
+ expect(listener).toHaveBeenCalledOnce();
66
+ });
67
+
68
+ it("deselect when already null does not notify", () => {
69
+ const store = new WorkspaceFileSelectionStore();
70
+ const listener = vi.fn();
71
+ store.subscribe(listener);
72
+
73
+ store.deselect();
74
+ expect(listener).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it("toggle selects when nothing is selected", () => {
78
+ const store = new WorkspaceFileSelectionStore();
79
+ store.toggle(file("ws-1", "a.ts"));
80
+ expect(store.getSelection()).toEqual(file("ws-1", "a.ts"));
81
+ });
82
+
83
+ it("toggle deselects when the same file is selected", () => {
84
+ const store = new WorkspaceFileSelectionStore();
85
+ store.select(file("ws-1", "a.ts"));
86
+ store.toggle(file("ws-1", "a.ts"));
87
+ expect(store.getSelection()).toBeNull();
88
+ });
89
+
90
+ it("toggle switches to a different file", () => {
91
+ const store = new WorkspaceFileSelectionStore();
92
+ store.select(file("ws-1", "a.ts"));
93
+ store.toggle(file("ws-2", "b.ts"));
94
+ expect(store.getSelection()).toEqual(file("ws-2", "b.ts"));
95
+ });
96
+
97
+ it("unsubscribe removes the listener", () => {
98
+ const store = new WorkspaceFileSelectionStore();
99
+ const listener = vi.fn();
100
+ const unsub = store.subscribe(listener);
101
+
102
+ unsub();
103
+ store.select(file("ws-1", "a.ts"));
104
+ expect(listener).not.toHaveBeenCalled();
105
+ });
106
+
107
+ it("supports multiple listeners", () => {
108
+ const store = new WorkspaceFileSelectionStore();
109
+ const a = vi.fn();
110
+ const b = vi.fn();
111
+ store.subscribe(a);
112
+ store.subscribe(b);
113
+
114
+ store.select(file("ws-1", "a.ts"));
115
+ expect(a).toHaveBeenCalledOnce();
116
+ expect(b).toHaveBeenCalledOnce();
117
+ });
118
+ });