@stigmer/react 3.1.1 → 3.1.3

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 (483) hide show
  1. package/composer/SessionComposer.d.ts +25 -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/ArtifactPreviewModal.d.ts +5 -5
  10. package/execution/ArtifactPreviewModal.js +4 -4
  11. package/execution/ArtifactPreviewModal.js.map +1 -1
  12. package/execution/FileChangeProgressBar.d.ts +47 -0
  13. package/execution/FileChangeProgressBar.d.ts.map +1 -0
  14. package/execution/FileChangeProgressBar.js +49 -0
  15. package/execution/FileChangeProgressBar.js.map +1 -0
  16. package/execution/FileChangesView.d.ts.map +1 -1
  17. package/execution/FileChangesView.js +19 -4
  18. package/execution/FileChangesView.js.map +1 -1
  19. package/execution/FilePathContext.d.ts +8 -1
  20. package/execution/FilePathContext.d.ts.map +1 -1
  21. package/execution/FilePathContext.js.map +1 -1
  22. package/execution/FilePathLink.d.ts.map +1 -1
  23. package/execution/FilePathLink.js +21 -6
  24. package/execution/FilePathLink.js.map +1 -1
  25. package/execution/FileReviewAtoms.d.ts +27 -0
  26. package/execution/FileReviewAtoms.d.ts.map +1 -0
  27. package/execution/FileReviewAtoms.js +64 -0
  28. package/execution/FileReviewAtoms.js.map +1 -0
  29. package/execution/FileReviewCard.d.ts +2 -2
  30. package/execution/FileReviewCard.d.ts.map +1 -1
  31. package/execution/FileReviewCard.js +32 -62
  32. package/execution/FileReviewCard.js.map +1 -1
  33. package/execution/MessageEntry.d.ts +20 -0
  34. package/execution/MessageEntry.d.ts.map +1 -1
  35. package/execution/MessageEntry.js +19 -5
  36. package/execution/MessageEntry.js.map +1 -1
  37. package/execution/MessageThread.d.ts +75 -13
  38. package/execution/MessageThread.d.ts.map +1 -1
  39. package/execution/MessageThread.js +195 -27
  40. package/execution/MessageThread.js.map +1 -1
  41. package/execution/PlanArtifactCard.d.ts +36 -21
  42. package/execution/PlanArtifactCard.d.ts.map +1 -1
  43. package/execution/PlanArtifactCard.js +39 -25
  44. package/execution/PlanArtifactCard.js.map +1 -1
  45. package/execution/PlanCompletionCard.d.ts +4 -4
  46. package/execution/PlanCompletionCard.d.ts.map +1 -1
  47. package/execution/PlanCompletionCard.js +4 -4
  48. package/execution/PlanCompletionCard.js.map +1 -1
  49. package/execution/PlanDocumentMessage.d.ts +32 -0
  50. package/execution/PlanDocumentMessage.d.ts.map +1 -0
  51. package/execution/PlanDocumentMessage.js +37 -0
  52. package/execution/PlanDocumentMessage.js.map +1 -0
  53. package/execution/PlanStreamingCard.d.ts +50 -0
  54. package/execution/PlanStreamingCard.d.ts.map +1 -0
  55. package/execution/PlanStreamingCard.js +40 -0
  56. package/execution/PlanStreamingCard.js.map +1 -0
  57. package/execution/index.d.ts +9 -1
  58. package/execution/index.d.ts.map +1 -1
  59. package/execution/index.js +4 -0
  60. package/execution/index.js.map +1 -1
  61. package/execution/use-build-from-plan-hotkey.d.ts +1 -1
  62. package/execution/use-build-from-plan-hotkey.js +1 -1
  63. package/execution/useArtifactCopy.d.ts +51 -0
  64. package/execution/useArtifactCopy.d.ts.map +1 -0
  65. package/execution/useArtifactCopy.js +75 -0
  66. package/execution/useArtifactCopy.js.map +1 -0
  67. package/execution/useArtifactDownload.d.ts.map +1 -1
  68. package/execution/useArtifactDownload.js +17 -5
  69. package/execution/useArtifactDownload.js.map +1 -1
  70. package/execution/useCreateAgentExecution.d.ts +12 -0
  71. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  72. package/execution/useCreateAgentExecution.js +5 -1
  73. package/execution/useCreateAgentExecution.js.map +1 -1
  74. package/github/decodeGitHubContent.d.ts +29 -0
  75. package/github/decodeGitHubContent.d.ts.map +1 -0
  76. package/github/decodeGitHubContent.js +72 -0
  77. package/github/decodeGitHubContent.js.map +1 -0
  78. package/github/index.d.ts +1 -0
  79. package/github/index.d.ts.map +1 -1
  80. package/github/index.js +1 -0
  81. package/github/index.js.map +1 -1
  82. package/github/useGitHubFileReader.d.ts +26 -0
  83. package/github/useGitHubFileReader.d.ts.map +1 -0
  84. package/github/useGitHubFileReader.js +106 -0
  85. package/github/useGitHubFileReader.js.map +1 -0
  86. package/github/useGitHubTreeLister.d.ts.map +1 -1
  87. package/github/useGitHubTreeLister.js +6 -2
  88. package/github/useGitHubTreeLister.js.map +1 -1
  89. package/index.d.ts +9 -9
  90. package/index.d.ts.map +1 -1
  91. package/index.js +5 -5
  92. package/index.js.map +1 -1
  93. package/internal/ResizableSplit.d.ts +72 -22
  94. package/internal/ResizableSplit.d.ts.map +1 -1
  95. package/internal/ResizableSplit.js +96 -38
  96. package/internal/ResizableSplit.js.map +1 -1
  97. package/internal/VirtualizedThread.d.ts +5 -3
  98. package/internal/VirtualizedThread.d.ts.map +1 -1
  99. package/internal/VirtualizedThread.js +6 -4
  100. package/internal/VirtualizedThread.js.map +1 -1
  101. package/internal/file-icons/index.d.ts +17 -0
  102. package/internal/file-icons/index.d.ts.map +1 -0
  103. package/internal/file-icons/index.js +60 -0
  104. package/internal/file-icons/index.js.map +1 -0
  105. package/internal/file-tree/FileTreeNode.d.ts +18 -1
  106. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  107. package/internal/file-tree/FileTreeNode.js +11 -2
  108. package/internal/file-tree/FileTreeNode.js.map +1 -1
  109. package/internal/markdown-components.d.ts +38 -1
  110. package/internal/markdown-components.d.ts.map +1 -1
  111. package/internal/markdown-components.js +86 -11
  112. package/internal/markdown-components.js.map +1 -1
  113. package/{workflow → internal}/motion-preference.d.ts +7 -4
  114. package/internal/motion-preference.d.ts.map +1 -0
  115. package/{workflow → internal}/motion-preference.js +7 -4
  116. package/internal/motion-preference.js.map +1 -0
  117. package/internal/store/index.d.ts +33 -0
  118. package/internal/store/index.d.ts.map +1 -1
  119. package/internal/store/index.js +59 -0
  120. package/internal/store/index.js.map +1 -1
  121. package/internal/store/workspace-editors-store.d.ts +145 -0
  122. package/internal/store/workspace-editors-store.d.ts.map +1 -0
  123. package/internal/store/workspace-editors-store.js +208 -0
  124. package/internal/store/workspace-editors-store.js.map +1 -0
  125. package/internal/store/workspace-file-selection-store.d.ts +38 -0
  126. package/internal/store/workspace-file-selection-store.d.ts.map +1 -0
  127. package/internal/store/workspace-file-selection-store.js +62 -0
  128. package/internal/store/workspace-file-selection-store.js.map +1 -0
  129. package/internal/useRevealLine.d.ts +49 -0
  130. package/internal/useRevealLine.d.ts.map +1 -0
  131. package/internal/useRevealLine.js +43 -0
  132. package/internal/useRevealLine.js.map +1 -0
  133. package/library/detect-plan-artifact.d.ts +43 -4
  134. package/library/detect-plan-artifact.d.ts.map +1 -1
  135. package/library/detect-plan-artifact.js +45 -5
  136. package/library/detect-plan-artifact.js.map +1 -1
  137. package/library/detect-streaming-plan.d.ts +45 -0
  138. package/library/detect-streaming-plan.d.ts.map +1 -0
  139. package/library/detect-streaming-plan.js +83 -0
  140. package/library/detect-streaming-plan.js.map +1 -0
  141. package/library/index.d.ts +4 -1
  142. package/library/index.d.ts.map +1 -1
  143. package/library/index.js +2 -1
  144. package/library/index.js.map +1 -1
  145. package/package.json +4 -4
  146. package/session/NewSessionViewer.d.ts +19 -4
  147. package/session/NewSessionViewer.d.ts.map +1 -1
  148. package/session/NewSessionViewer.js +52 -25
  149. package/session/NewSessionViewer.js.map +1 -1
  150. package/session/PlanEditor.d.ts +55 -0
  151. package/session/PlanEditor.d.ts.map +1 -0
  152. package/session/PlanEditor.js +88 -0
  153. package/session/PlanEditor.js.map +1 -0
  154. package/session/PlanStreamingDocument.d.ts +30 -0
  155. package/session/PlanStreamingDocument.d.ts.map +1 -0
  156. package/session/PlanStreamingDocument.js +37 -0
  157. package/session/PlanStreamingDocument.js.map +1 -0
  158. package/session/SessionPanelChip.d.ts +26 -0
  159. package/session/SessionPanelChip.d.ts.map +1 -0
  160. package/session/SessionPanelChip.js +40 -0
  161. package/session/SessionPanelChip.js.map +1 -0
  162. package/session/SessionViewer.d.ts +34 -7
  163. package/session/SessionViewer.d.ts.map +1 -1
  164. package/session/SessionViewer.js +334 -33
  165. package/session/SessionViewer.js.map +1 -1
  166. package/session/{inspector → facets}/ArtifactsTab.d.ts +10 -2
  167. package/session/facets/ArtifactsTab.d.ts.map +1 -0
  168. package/session/{inspector → facets}/ArtifactsTab.js +8 -3
  169. package/session/facets/ArtifactsTab.js.map +1 -0
  170. package/session/{inspector → facets}/ChangesTab.d.ts +3 -3
  171. package/session/facets/ChangesTab.d.ts.map +1 -0
  172. package/session/{inspector → facets}/ChangesTab.js +3 -3
  173. package/session/facets/ChangesTab.js.map +1 -0
  174. package/session/facets/InspectTab.d.ts.map +1 -0
  175. package/session/facets/InspectTab.js.map +1 -0
  176. package/session/{inspector → facets}/SetupTab.d.ts +11 -2
  177. package/session/facets/SetupTab.d.ts.map +1 -0
  178. package/session/{inspector → facets}/SetupTab.js +4 -3
  179. package/session/facets/SetupTab.js.map +1 -0
  180. package/session/{inspector → facets}/UsageTab.d.ts +1 -1
  181. package/session/facets/UsageTab.d.ts.map +1 -0
  182. package/session/{inspector → facets}/UsageTab.js +1 -1
  183. package/session/facets/UsageTab.js.map +1 -0
  184. package/session/{inspector → facets}/index.d.ts +0 -6
  185. package/session/facets/index.d.ts.map +1 -0
  186. package/session/{inspector → facets}/index.js +0 -3
  187. package/session/facets/index.js.map +1 -0
  188. package/session/index.d.ts +11 -2
  189. package/session/index.d.ts.map +1 -1
  190. package/session/index.js +15 -2
  191. package/session/index.js.map +1 -1
  192. package/session/plan-document.d.ts +21 -0
  193. package/session/plan-document.d.ts.map +1 -0
  194. package/session/plan-document.js +28 -0
  195. package/session/plan-document.js.map +1 -0
  196. package/session/useOpenFileChange.d.ts +20 -0
  197. package/session/useOpenFileChange.d.ts.map +1 -0
  198. package/session/useOpenFileChange.js +31 -0
  199. package/session/useOpenFileChange.js.map +1 -0
  200. package/session/usePlanDraft.d.ts +52 -0
  201. package/session/usePlanDraft.d.ts.map +1 -0
  202. package/session/usePlanDraft.js +63 -0
  203. package/session/usePlanDraft.js.map +1 -0
  204. package/session/useSessionConversation.d.ts +14 -1
  205. package/session/useSessionConversation.d.ts.map +1 -1
  206. package/session/useSessionConversation.js +20 -2
  207. package/session/useSessionConversation.js.map +1 -1
  208. package/session/useSessionPageFlow.d.ts.map +1 -1
  209. package/session/useSessionPageFlow.js +1 -0
  210. package/session/useSessionPageFlow.js.map +1 -1
  211. package/session/useSessionPanel.d.ts +120 -0
  212. package/session/useSessionPanel.d.ts.map +1 -0
  213. package/session/useSessionPanel.js +130 -0
  214. package/session/useSessionPanel.js.map +1 -0
  215. package/session/useSessionRailViews.d.ts +45 -0
  216. package/session/useSessionRailViews.d.ts.map +1 -0
  217. package/session/useSessionRailViews.js +105 -0
  218. package/session/useSessionRailViews.js.map +1 -0
  219. package/src/composer/SessionComposer.tsx +53 -10
  220. package/src/composer/__tests__/SessionComposer-contract.test.tsx +58 -4
  221. package/src/execution/ArtifactContentRenderer.tsx +101 -24
  222. package/src/execution/ArtifactPreviewModal.tsx +9 -9
  223. package/src/execution/FileChangeProgressBar.tsx +121 -0
  224. package/src/execution/FileChangesView.tsx +18 -4
  225. package/src/execution/FilePathContext.tsx +8 -1
  226. package/src/execution/FilePathLink.tsx +22 -6
  227. package/src/execution/FileReviewAtoms.tsx +89 -0
  228. package/src/execution/FileReviewCard.tsx +57 -90
  229. package/src/execution/MessageEntry.tsx +49 -1
  230. package/src/execution/MessageThread.tsx +308 -39
  231. package/src/execution/PlanArtifactCard.tsx +174 -64
  232. package/src/execution/PlanCompletionCard.tsx +5 -5
  233. package/src/execution/PlanDocumentMessage.tsx +96 -0
  234. package/src/execution/PlanStreamingCard.tsx +141 -0
  235. package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +80 -1
  236. package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +6 -6
  237. package/src/execution/__tests__/FileChangeProgressBar.test.tsx +125 -0
  238. package/src/execution/__tests__/FilePathLink.test.tsx +87 -0
  239. package/src/execution/__tests__/FileReviewCard.test.tsx +66 -7
  240. package/src/execution/__tests__/MessageThread.test.tsx +102 -1
  241. package/src/execution/__tests__/PlanArtifactCard.test.tsx +114 -26
  242. package/src/execution/__tests__/PlanCompletionCard.test.tsx +2 -2
  243. package/src/execution/__tests__/PlanDocumentMessage.test.tsx +55 -0
  244. package/src/execution/__tests__/PlanStreamingCard.test.tsx +56 -0
  245. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +600 -22
  246. package/src/execution/__tests__/message-entry.test.tsx +17 -0
  247. package/src/execution/__tests__/useArtifactCopy.test.tsx +90 -0
  248. package/src/execution/__tests__/useArtifactDownload.test.tsx +2 -0
  249. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +85 -0
  250. package/src/execution/index.ts +13 -1
  251. package/src/execution/use-build-from-plan-hotkey.ts +1 -1
  252. package/src/execution/useArtifactCopy.ts +109 -0
  253. package/src/execution/useArtifactDownload.ts +17 -5
  254. package/src/execution/useCreateAgentExecution.ts +18 -1
  255. package/src/github/__tests__/decodeGitHubContent.test.ts +102 -0
  256. package/src/github/__tests__/useGitHubFileReader.test.ts +183 -0
  257. package/src/github/decodeGitHubContent.ts +83 -0
  258. package/src/github/index.ts +1 -0
  259. package/src/github/useGitHubFileReader.ts +152 -0
  260. package/src/github/useGitHubTreeLister.ts +6 -2
  261. package/src/index.ts +69 -9
  262. package/src/internal/ResizableSplit.tsx +184 -51
  263. package/src/internal/VirtualizedThread.tsx +12 -4
  264. package/src/internal/__tests__/ResizableSplit.test.tsx +125 -3
  265. package/src/internal/__tests__/markdown-components.test.tsx +68 -0
  266. package/src/internal/__tests__/useRevealLine.test.tsx +97 -0
  267. package/src/internal/file-icons/__tests__/file-icons.test.tsx +35 -0
  268. package/src/internal/file-icons/index.tsx +137 -0
  269. package/src/internal/file-tree/FileTreeNode.tsx +56 -5
  270. package/src/internal/file-tree/__tests__/FileTreeNode-explorer.test.tsx +86 -0
  271. package/src/internal/markdown-components.tsx +116 -11
  272. package/src/{workflow → internal}/motion-preference.ts +7 -4
  273. package/src/internal/store/__tests__/workspace-editors-store.test.ts +216 -0
  274. package/src/internal/store/__tests__/workspace-file-selection-store.test.ts +118 -0
  275. package/src/internal/store/index.ts +94 -0
  276. package/src/internal/store/workspace-editors-store.ts +315 -0
  277. package/src/internal/store/workspace-file-selection-store.ts +88 -0
  278. package/src/internal/useRevealLine.ts +81 -0
  279. package/src/library/__tests__/detect-plan-artifact.test.ts +69 -0
  280. package/src/library/__tests__/detect-streaming-plan.test.ts +234 -0
  281. package/src/library/detect-plan-artifact.ts +62 -5
  282. package/src/library/detect-streaming-plan.ts +108 -0
  283. package/src/library/index.ts +7 -0
  284. package/src/session/NewSessionViewer.tsx +123 -55
  285. package/src/session/PlanEditor.tsx +361 -0
  286. package/src/session/PlanStreamingDocument.tsx +97 -0
  287. package/src/session/SessionPanelChip.tsx +103 -0
  288. package/src/session/SessionViewer.tsx +655 -96
  289. package/src/session/__tests__/NewSessionViewer.test.tsx +148 -0
  290. package/src/session/__tests__/PlanEditor.test.tsx +217 -0
  291. package/src/session/__tests__/PlanStreamingDocument.test.tsx +50 -0
  292. package/src/session/__tests__/SessionPanelChip.test.tsx +42 -0
  293. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +66 -4
  294. package/src/session/__tests__/SessionViewer-transcriptFileOpen.test.tsx +205 -0
  295. package/src/session/__tests__/audienceWiring.test.tsx +55 -19
  296. package/src/session/__tests__/useOpenFileChange.test.tsx +82 -0
  297. package/src/session/__tests__/usePlanDraft.test.ts +102 -0
  298. package/src/session/__tests__/useSessionConversation.test.tsx +44 -0
  299. package/src/session/__tests__/useSessionPanel.test.ts +298 -0
  300. package/src/session/__tests__/useSessionRailViews.test.tsx +124 -0
  301. package/src/session/{inspector → facets}/ArtifactsTab.tsx +27 -5
  302. package/src/session/{inspector → facets}/ChangesTab.tsx +3 -3
  303. package/src/session/{inspector → facets}/SetupTab.tsx +13 -1
  304. package/src/session/{inspector → facets}/UsageTab.tsx +1 -1
  305. package/src/session/{inspector → facets}/__tests__/ArtifactsTab.test.tsx +42 -8
  306. package/src/session/facets/__tests__/SetupTab.test.tsx +44 -0
  307. package/src/session/{inspector → facets}/index.ts +0 -16
  308. package/src/session/index.ts +30 -13
  309. package/src/session/plan-document.ts +30 -0
  310. package/src/session/useOpenFileChange.ts +53 -0
  311. package/src/session/usePlanDraft.ts +109 -0
  312. package/src/session/useSessionConversation.ts +38 -3
  313. package/src/session/useSessionPageFlow.ts +1 -0
  314. package/src/session/useSessionPanel.ts +288 -0
  315. package/src/session/useSessionRailViews.tsx +200 -0
  316. package/src/workflow/WorkflowExecutionGraph.tsx +1 -1
  317. package/src/workflow/index.ts +1 -1
  318. package/src/workflow/useFollowExecution.ts +1 -1
  319. package/src/workspace/EditorTabs.tsx +174 -0
  320. package/src/workspace/ExplorerTree.tsx +253 -0
  321. package/src/workspace/FileViewer.tsx +537 -0
  322. package/src/workspace/WorkspaceContentSearch.tsx +502 -0
  323. package/src/workspace/WorkspaceContentSearcher.ts +70 -0
  324. package/src/workspace/WorkspaceEditor.tsx +19 -16
  325. package/src/workspace/WorkspaceFileLister.ts +11 -0
  326. package/src/workspace/WorkspaceFileReader.ts +57 -0
  327. package/src/workspace/WorkspaceFileSearch.tsx +452 -0
  328. package/src/workspace/WorkspaceSurface.tsx +839 -0
  329. package/src/workspace/__tests__/EditorTabs.test.tsx +102 -0
  330. package/src/workspace/__tests__/ExplorerTree.test.tsx +204 -0
  331. package/src/workspace/__tests__/FileViewer.test.tsx +439 -0
  332. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +229 -0
  333. package/src/workspace/__tests__/WorkspaceEditor-initialPanel.test.tsx +19 -0
  334. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +227 -0
  335. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +418 -0
  336. package/src/workspace/__tests__/a11y/harness.tsx +140 -0
  337. package/src/workspace/__tests__/a11y/mock-capabilities.ts +138 -0
  338. package/src/workspace/__tests__/a11y/workspace-editor.a11y.test.tsx +65 -0
  339. package/src/workspace/__tests__/a11y/workspace-explorer-search.a11y.test.tsx +112 -0
  340. package/src/workspace/__tests__/a11y/workspace-viewer-tabs.a11y.test.tsx +69 -0
  341. package/src/workspace/__tests__/findChangeForSelection.test.ts +132 -0
  342. package/src/workspace/__tests__/matchContentPreview.test.ts +55 -0
  343. package/src/workspace/__tests__/matchWorkspaceFiles.test.ts +104 -0
  344. package/src/workspace/__tests__/resolveWorkspaceFileSelection.test.ts +135 -0
  345. package/src/workspace/__tests__/useWorkspaceContentSearch.test.tsx +196 -0
  346. package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +190 -0
  347. package/src/workspace/__tests__/useWorkspaceFileSearch.test.tsx +162 -0
  348. package/src/workspace/findChangeForSelection.ts +61 -0
  349. package/src/workspace/index.ts +56 -2
  350. package/src/workspace/matchContentPreview.ts +51 -0
  351. package/src/workspace/matchWorkspaceFiles.ts +86 -0
  352. package/src/workspace/resolveWorkspaceFileSelection.ts +119 -0
  353. package/src/workspace/useWorkspaceContentSearch.ts +228 -0
  354. package/src/workspace/useWorkspaceFileContent.ts +129 -0
  355. package/src/workspace/useWorkspaceFileSearch.ts +184 -0
  356. package/src/workspace/useWorkspaceFiles.ts +27 -27
  357. package/src/workspace/workspaceListingCache.ts +93 -0
  358. package/styles.css +1 -1
  359. package/workflow/WorkflowExecutionGraph.js +1 -1
  360. package/workflow/WorkflowExecutionGraph.js.map +1 -1
  361. package/workflow/index.d.ts +1 -1
  362. package/workflow/index.d.ts.map +1 -1
  363. package/workflow/index.js +1 -1
  364. package/workflow/index.js.map +1 -1
  365. package/workflow/useFollowExecution.js +1 -1
  366. package/workflow/useFollowExecution.js.map +1 -1
  367. package/workspace/EditorTabs.d.ts +47 -0
  368. package/workspace/EditorTabs.d.ts.map +1 -0
  369. package/workspace/EditorTabs.js +78 -0
  370. package/workspace/EditorTabs.js.map +1 -0
  371. package/workspace/ExplorerTree.d.ts +32 -0
  372. package/workspace/ExplorerTree.d.ts.map +1 -0
  373. package/workspace/ExplorerTree.js +49 -0
  374. package/workspace/ExplorerTree.js.map +1 -0
  375. package/workspace/FileViewer.d.ts +93 -0
  376. package/workspace/FileViewer.d.ts.map +1 -0
  377. package/workspace/FileViewer.js +193 -0
  378. package/workspace/FileViewer.js.map +1 -0
  379. package/workspace/WorkspaceContentSearch.d.ts +41 -0
  380. package/workspace/WorkspaceContentSearch.d.ts.map +1 -0
  381. package/workspace/WorkspaceContentSearch.js +216 -0
  382. package/workspace/WorkspaceContentSearch.js.map +1 -0
  383. package/workspace/WorkspaceContentSearcher.d.ts +59 -0
  384. package/workspace/WorkspaceContentSearcher.d.ts.map +1 -0
  385. package/workspace/WorkspaceContentSearcher.js +7 -0
  386. package/workspace/WorkspaceContentSearcher.js.map +1 -0
  387. package/workspace/WorkspaceEditor.d.ts.map +1 -1
  388. package/workspace/WorkspaceEditor.js +8 -10
  389. package/workspace/WorkspaceEditor.js.map +1 -1
  390. package/workspace/WorkspaceFileLister.d.ts +11 -0
  391. package/workspace/WorkspaceFileLister.d.ts.map +1 -1
  392. package/workspace/WorkspaceFileReader.d.ts +48 -0
  393. package/workspace/WorkspaceFileReader.d.ts.map +1 -0
  394. package/workspace/WorkspaceFileReader.js +11 -0
  395. package/workspace/WorkspaceFileReader.js.map +1 -0
  396. package/workspace/WorkspaceFileSearch.d.ts +32 -0
  397. package/workspace/WorkspaceFileSearch.d.ts.map +1 -0
  398. package/workspace/WorkspaceFileSearch.js +175 -0
  399. package/workspace/WorkspaceFileSearch.js.map +1 -0
  400. package/workspace/WorkspaceSurface.d.ts +151 -0
  401. package/workspace/WorkspaceSurface.d.ts.map +1 -0
  402. package/workspace/WorkspaceSurface.js +220 -0
  403. package/workspace/WorkspaceSurface.js.map +1 -0
  404. package/workspace/findChangeForSelection.d.ts +19 -0
  405. package/workspace/findChangeForSelection.d.ts.map +1 -0
  406. package/workspace/findChangeForSelection.js +46 -0
  407. package/workspace/findChangeForSelection.js.map +1 -0
  408. package/workspace/index.d.ts +24 -2
  409. package/workspace/index.d.ts.map +1 -1
  410. package/workspace/index.js +13 -1
  411. package/workspace/index.js.map +1 -1
  412. package/workspace/matchContentPreview.d.ts +21 -0
  413. package/workspace/matchContentPreview.d.ts.map +1 -0
  414. package/workspace/matchContentPreview.js +36 -0
  415. package/workspace/matchContentPreview.js.map +1 -0
  416. package/workspace/matchWorkspaceFiles.d.ts +30 -0
  417. package/workspace/matchWorkspaceFiles.d.ts.map +1 -0
  418. package/workspace/matchWorkspaceFiles.js +58 -0
  419. package/workspace/matchWorkspaceFiles.js.map +1 -0
  420. package/workspace/resolveWorkspaceFileSelection.d.ts +19 -0
  421. package/workspace/resolveWorkspaceFileSelection.d.ts.map +1 -0
  422. package/workspace/resolveWorkspaceFileSelection.js +115 -0
  423. package/workspace/resolveWorkspaceFileSelection.js.map +1 -0
  424. package/workspace/useWorkspaceContentSearch.d.ts +69 -0
  425. package/workspace/useWorkspaceContentSearch.d.ts.map +1 -0
  426. package/workspace/useWorkspaceContentSearch.js +154 -0
  427. package/workspace/useWorkspaceContentSearch.js.map +1 -0
  428. package/workspace/useWorkspaceFileContent.d.ts +73 -0
  429. package/workspace/useWorkspaceFileContent.d.ts.map +1 -0
  430. package/workspace/useWorkspaceFileContent.js +57 -0
  431. package/workspace/useWorkspaceFileContent.js.map +1 -0
  432. package/workspace/useWorkspaceFileSearch.d.ts +61 -0
  433. package/workspace/useWorkspaceFileSearch.d.ts.map +1 -0
  434. package/workspace/useWorkspaceFileSearch.js +119 -0
  435. package/workspace/useWorkspaceFileSearch.js.map +1 -0
  436. package/workspace/useWorkspaceFiles.d.ts +13 -5
  437. package/workspace/useWorkspaceFiles.d.ts.map +1 -1
  438. package/workspace/useWorkspaceFiles.js +18 -19
  439. package/workspace/useWorkspaceFiles.js.map +1 -1
  440. package/workspace/workspaceListingCache.d.ts +47 -0
  441. package/workspace/workspaceListingCache.d.ts.map +1 -0
  442. package/workspace/workspaceListingCache.js +55 -0
  443. package/workspace/workspaceListingCache.js.map +1 -0
  444. package/session/inspector/ArtifactsTab.d.ts.map +0 -1
  445. package/session/inspector/ArtifactsTab.js.map +0 -1
  446. package/session/inspector/ChangesTab.d.ts.map +0 -1
  447. package/session/inspector/ChangesTab.js.map +0 -1
  448. package/session/inspector/InspectTab.d.ts.map +0 -1
  449. package/session/inspector/InspectTab.js.map +0 -1
  450. package/session/inspector/SessionInspector.d.ts +0 -64
  451. package/session/inspector/SessionInspector.d.ts.map +0 -1
  452. package/session/inspector/SessionInspector.js +0 -53
  453. package/session/inspector/SessionInspector.js.map +0 -1
  454. package/session/inspector/SetupTab.d.ts.map +0 -1
  455. package/session/inspector/SetupTab.js.map +0 -1
  456. package/session/inspector/UsageTab.d.ts.map +0 -1
  457. package/session/inspector/UsageTab.js.map +0 -1
  458. package/session/inspector/WorkspaceTab.d.ts +0 -35
  459. package/session/inspector/WorkspaceTab.d.ts.map +0 -1
  460. package/session/inspector/WorkspaceTab.js +0 -75
  461. package/session/inspector/WorkspaceTab.js.map +0 -1
  462. package/session/inspector/index.d.ts.map +0 -1
  463. package/session/inspector/index.js.map +0 -1
  464. package/session/inspector/useSessionInspector.d.ts +0 -49
  465. package/session/inspector/useSessionInspector.d.ts.map +0 -1
  466. package/session/inspector/useSessionInspector.js +0 -122
  467. package/session/inspector/useSessionInspector.js.map +0 -1
  468. package/src/session/inspector/SessionInspector.tsx +0 -162
  469. package/src/session/inspector/WorkspaceTab.tsx +0 -245
  470. package/src/session/inspector/__tests__/useSessionInspector.test.ts +0 -251
  471. package/src/session/inspector/useSessionInspector.ts +0 -178
  472. package/src/workspace/WorkspaceEntryFiles.tsx +0 -196
  473. package/workflow/motion-preference.d.ts.map +0 -1
  474. package/workflow/motion-preference.js.map +0 -1
  475. package/workspace/WorkspaceEntryFiles.d.ts +0 -22
  476. package/workspace/WorkspaceEntryFiles.d.ts.map +0 -1
  477. package/workspace/WorkspaceEntryFiles.js +0 -58
  478. package/workspace/WorkspaceEntryFiles.js.map +0 -1
  479. /package/session/{inspector → facets}/InspectTab.d.ts +0 -0
  480. /package/session/{inspector → facets}/InspectTab.js +0 -0
  481. /package/src/{workflow → internal}/__tests__/motion-preference.test.ts +0 -0
  482. /package/src/session/{inspector → facets}/InspectTab.tsx +0 -0
  483. /package/src/session/{inspector → facets}/__tests__/ChangesTab.test.tsx +0 -0
@@ -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
+ });
@@ -3,6 +3,14 @@
3
3
  import { createContext, useContext, useRef, useSyncExternalStore } from "react";
4
4
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
5
5
  import { ConversationStore, type StreamState } from "./conversation-store.js";
6
+ import {
7
+ WorkspaceFileSelectionStore,
8
+ type SelectedWorkspaceFile,
9
+ } from "./workspace-file-selection-store.js";
10
+ import {
11
+ WorkspaceEditorsStore,
12
+ type WorkspaceEditorsSnapshot,
13
+ } from "./workspace-editors-store.js";
6
14
 
7
15
  export { ConversationStore, type StreamState } from "./conversation-store.js";
8
16
  export { structuralShare } from "./structural-share.js";
@@ -16,6 +24,22 @@ export {
16
24
  export { SelectionStore } from "./selection-store.js";
17
25
  export type { SelectedThreadItem } from "./selection-store.js";
18
26
 
27
+ export { WorkspaceFileSelectionStore } from "./workspace-file-selection-store.js";
28
+ export type { SelectedWorkspaceFile } from "./workspace-file-selection-store.js";
29
+
30
+ export {
31
+ WorkspaceEditorsStore,
32
+ editorKey,
33
+ virtualEntryId,
34
+ isVirtualEntryId,
35
+ } from "./workspace-editors-store.js";
36
+ export type {
37
+ OpenEditor,
38
+ OpenFileOptions,
39
+ EditorReveal,
40
+ WorkspaceEditorsSnapshot,
41
+ } from "./workspace-editors-store.js";
42
+
19
43
  // ---------------------------------------------------------------------------
20
44
  // Context
21
45
  // ---------------------------------------------------------------------------
@@ -75,3 +99,73 @@ export function useStoreExecution(
75
99
  export function useStoreStreamState(store: ConversationStore): StreamState {
76
100
  return useSyncExternalStore(store.subscribe, store.getStreamState);
77
101
  }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Workspace file selection — the DD-07 shared "which file is open" store
105
+ // ---------------------------------------------------------------------------
106
+
107
+ /**
108
+ * Internal context carrying the {@link WorkspaceFileSelectionStore} instance
109
+ * (stable ref) to both columns of the session viewer. Not exported from the
110
+ * public SDK barrel — consumed only within SDK components and `SessionViewer`.
111
+ */
112
+ export const WorkspaceFileSelectionContext =
113
+ createContext<WorkspaceFileSelectionStore | null>(null);
114
+
115
+ /**
116
+ * Access the workspace-file selection store from context.
117
+ * Throws if called outside a `WorkspaceFileSelectionContext.Provider`.
118
+ */
119
+ export function useWorkspaceFileSelectionStore(): WorkspaceFileSelectionStore {
120
+ const store = useContext(WorkspaceFileSelectionContext);
121
+ if (!store) {
122
+ throw new Error(
123
+ "useWorkspaceFileSelectionStore must be used within a WorkspaceFileSelectionContext.Provider",
124
+ );
125
+ }
126
+ return store;
127
+ }
128
+
129
+ /**
130
+ * Create or reuse a `WorkspaceFileSelectionStore` instance, preserved across
131
+ * re-renders via ref. `SessionViewer` calls this and provides the result
132
+ * through {@link WorkspaceFileSelectionContext} (DD-07).
133
+ */
134
+ export function useWorkspaceFileSelectionStoreRef(): WorkspaceFileSelectionStore {
135
+ const ref = useRef<WorkspaceFileSelectionStore | null>(null);
136
+ if (!ref.current) {
137
+ ref.current = new WorkspaceFileSelectionStore();
138
+ }
139
+ return ref.current;
140
+ }
141
+
142
+ /** Subscribe to the currently-selected workspace file. */
143
+ export function useWorkspaceFileSelection(
144
+ store: WorkspaceFileSelectionStore,
145
+ ): SelectedWorkspaceFile | null {
146
+ return useSyncExternalStore(store.subscribe, store.getSelection);
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Workspace editors — the surface's open-editor group (Slice B)
151
+ // ---------------------------------------------------------------------------
152
+
153
+ /**
154
+ * Create or reuse a `WorkspaceEditorsStore` instance, preserved across
155
+ * re-renders via ref. Owned by `SessionViewer` alongside the selection store;
156
+ * the surface subscribes to it for its tab group.
157
+ */
158
+ export function useWorkspaceEditorsStoreRef(): WorkspaceEditorsStore {
159
+ const ref = useRef<WorkspaceEditorsStore | null>(null);
160
+ if (!ref.current) {
161
+ ref.current = new WorkspaceEditorsStore();
162
+ }
163
+ return ref.current;
164
+ }
165
+
166
+ /** Subscribe to the open-editor group snapshot. */
167
+ export function useWorkspaceEditors(
168
+ store: WorkspaceEditorsStore,
169
+ ): WorkspaceEditorsSnapshot {
170
+ return useSyncExternalStore(store.subscribe, store.getSnapshot);
171
+ }