@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
@@ -0,0 +1,315 @@
1
+ // The set of workspace files open in the surface's editor group, plus which is
2
+ // active — the VS Code "open editors" model. Sibling to
3
+ // WorkspaceFileSelectionStore (which stays the single-file selection used by the
4
+ // launcher's Viewer tab); this store owns the multi-tab surface. Its active
5
+ // editor derives the same SelectedWorkspaceFile shape, so downstream diff
6
+ // correlation and tree highlight consume one selection regardless of source.
7
+
8
+ import type { RevealTarget } from "../useRevealLine.js";
9
+ import type { SelectedWorkspaceFile } from "./workspace-file-selection-store.js";
10
+
11
+ /**
12
+ * Options for opening a file in the surface (via `openPreview` / the surface's
13
+ * `onOpenFile` seam). `line` requests a jump-to-line reveal: the file opens in
14
+ * a line-faithful view scrolled to and highlighting that 1-based line. Absent,
15
+ * the file opens normally (top of file / diff-default). Deliberately an options
16
+ * object so future open-intents (e.g. a column) extend it without churning the
17
+ * callback signature.
18
+ */
19
+ export interface OpenFileOptions {
20
+ /** 1-based line to reveal after opening. */
21
+ readonly line?: number;
22
+ }
23
+
24
+ /**
25
+ * A pending reveal request scoped to a specific editor. Extends the viewer-side
26
+ * {@link RevealTarget} with the `key` of the editor it targets, so a consumer
27
+ * applies the reveal only while that editor is the active one. Transient
28
+ * navigation, never editor identity (the line does not key the tab).
29
+ */
30
+ export interface EditorReveal extends RevealTarget {
31
+ /** {@link editorKey} of the editor this reveal targets. */
32
+ readonly key: string;
33
+ }
34
+
35
+ /**
36
+ * One open editor tab.
37
+ *
38
+ * `preview` marks the single reusable "preview" tab (shown in italics): a
39
+ * single click opens or replaces it in place, so casual browsing never
40
+ * accumulates tabs. Pinning it (double-click, or opening the file explicitly)
41
+ * clears the flag and the tab becomes persistent. At most one editor is a
42
+ * preview at any time.
43
+ */
44
+ export interface OpenEditor {
45
+ /** Stable-within-page id of the owning workspace entry. */
46
+ readonly entryId: string;
47
+ /** Repo-relative (git) or root-relative (local) file path. */
48
+ readonly path: string;
49
+ /** Whether this is the reusable preview tab (italic, single per group). */
50
+ readonly preview: boolean;
51
+ }
52
+
53
+ /** Immutable snapshot for the `useSyncExternalStore` contract. */
54
+ export interface WorkspaceEditorsSnapshot {
55
+ /** Open editors in tab order. */
56
+ readonly editors: readonly OpenEditor[];
57
+ /** Key of the active editor, or `null` when the group is empty. */
58
+ readonly activeKey: string | null;
59
+ /**
60
+ * The active editor as a {@link SelectedWorkspaceFile} (stable ref between
61
+ * mutations), or `null`. This is what the viewer, diff correlation, and tree
62
+ * highlight consume — the editors store's projection into the selection shape.
63
+ */
64
+ readonly activeFile: SelectedWorkspaceFile | null;
65
+ /**
66
+ * A pending jump-to-line reveal, or `null`. Set only by an open-with-line
67
+ * (`openPreview` with a `line`); cleared by any other active-editor change so
68
+ * returning to a tab never re-scrolls to a stale line. Consumers honor it only
69
+ * while `reveal.key` matches {@link activeKey}. Transient — not part of any
70
+ * editor's identity.
71
+ */
72
+ readonly reveal: EditorReveal | null;
73
+ }
74
+
75
+ type Listener = () => void;
76
+
77
+ const EMPTY_SNAPSHOT: WorkspaceEditorsSnapshot = {
78
+ editors: [],
79
+ activeKey: null,
80
+ activeFile: null,
81
+ reveal: null,
82
+ };
83
+
84
+ /**
85
+ * Stable key for an editor. Uses NUL as the separator — legal in neither an
86
+ * entry id nor a file path — so `entryId`/`path` can never collide.
87
+ */
88
+ export function editorKey(entryId: string, path: string): string {
89
+ return `${entryId}\u0000${path}`;
90
+ }
91
+
92
+ /**
93
+ * NUL-prefixed namespace for virtual-document entry ids — the same
94
+ * collision-proofing trick as {@link editorKey}: no real workspace entry id
95
+ * can contain NUL, so a virtual document can never alias a workspace file.
96
+ */
97
+ const VIRTUAL_ENTRY_PREFIX = "\u0000virtual:";
98
+
99
+ /**
100
+ * Entry id for a virtual document — an editor tab whose content is not a
101
+ * workspace file (e.g. the session's `plan.md`, an execution artifact).
102
+ * Virtual documents share the editor group with file tabs (same open / pin /
103
+ * close / activate semantics); only their *content rendering* differs, via the
104
+ * surface's `virtualDocuments` seam. `kind` names the document family
105
+ * (`"plan"` today) so future virtual documents get distinct identities.
106
+ */
107
+ export function virtualEntryId(kind: string): string {
108
+ return `${VIRTUAL_ENTRY_PREFIX}${kind}`;
109
+ }
110
+
111
+ /**
112
+ * True when an entry id names a virtual document rather than a workspace
113
+ * entry. Used by file-only consumers (diff correlation, tree highlight,
114
+ * reveal) to skip work that can only apply to real files.
115
+ */
116
+ export function isVirtualEntryId(entryId: string): boolean {
117
+ return entryId.startsWith(VIRTUAL_ENTRY_PREFIX);
118
+ }
119
+
120
+ /**
121
+ * Framework-agnostic store for the surface's open-editor group.
122
+ *
123
+ * Implements the `useSyncExternalStore` contract like the sibling selection and
124
+ * conversation stores: the context/ref carries the instance, and `getSnapshot`
125
+ * returns a snapshot object that is replaced only inside a mutation — so it is a
126
+ * stable reference between mutations and never triggers a render loop.
127
+ *
128
+ * VS Code preview-tab semantics:
129
+ * - {@link openPreview} — single click: reuse the one preview slot (or focus an
130
+ * already-open editor); casual browsing stays at one tab.
131
+ * - {@link openPinned} / {@link pin} — double-click or explicit open: the tab
132
+ * becomes persistent.
133
+ * - {@link close} — remove a tab, activating a neighbor.
134
+ * - {@link activate} — focus an already-open tab.
135
+ */
136
+ export class WorkspaceEditorsStore {
137
+ private _snapshot: WorkspaceEditorsSnapshot = EMPTY_SNAPSHOT;
138
+ private _listeners = new Set<Listener>();
139
+ /** Monotonic reveal token — bumped on every open-with-line (see `openPreview`). */
140
+ private _revealSeq = 0;
141
+
142
+ // -- Mutations -----------------------------------------------------------
143
+
144
+ /**
145
+ * Single-click open: reuse the preview slot, or focus if already open.
146
+ *
147
+ * When `options.line` is set, this also records a jump-to-line reveal for the
148
+ * opened editor. The reveal nonce is bumped **every** time — even when the
149
+ * file is already open and active — so a second search hit in the same file
150
+ * re-scrolls the already-mounted viewer. An open without a `line` clears any
151
+ * pending reveal.
152
+ */
153
+ openPreview(entryId: string, path: string, options?: OpenFileOptions): void {
154
+ const key = editorKey(entryId, path);
155
+ const { editors } = this._snapshot;
156
+ const reveal = this._makeReveal(key, options);
157
+
158
+ if (editors.some((e) => editorKey(e.entryId, e.path) === key)) {
159
+ // Already open — focus it (preserving pinned/preview state) and apply the
160
+ // reveal (or clear it). Reuse the same editors array ref so a reveal-only
161
+ // update doesn't churn the tab list.
162
+ this._commit(editors, key, reveal);
163
+ return;
164
+ }
165
+
166
+ const next = { entryId, path, preview: true };
167
+ const previewIndex = editors.findIndex((e) => e.preview);
168
+ const editorsNext =
169
+ previewIndex >= 0
170
+ ? editors.map((e, i) => (i === previewIndex ? next : e))
171
+ : [...editors, next];
172
+ this._commit(editorsNext, key, reveal);
173
+ }
174
+
175
+ /** Explicit open (double-click / open-to-keep): a persistent tab. */
176
+ openPinned(entryId: string, path: string): void {
177
+ const key = editorKey(entryId, path);
178
+ const { editors } = this._snapshot;
179
+ const index = editors.findIndex((e) => editorKey(e.entryId, e.path) === key);
180
+
181
+ if (index >= 0) {
182
+ if (editors[index].preview) {
183
+ this._commit(
184
+ editors.map((e, i) => (i === index ? { ...e, preview: false } : e)),
185
+ key,
186
+ );
187
+ } else {
188
+ this._setActive(key);
189
+ }
190
+ return;
191
+ }
192
+
193
+ this._commit([...editors, { entryId, path, preview: false }], key);
194
+ }
195
+
196
+ /** Pin an open editor (clear its preview flag), keeping it active. */
197
+ pin(entryId: string, path: string): void {
198
+ const key = editorKey(entryId, path);
199
+ const { editors } = this._snapshot;
200
+ const index = editors.findIndex((e) => editorKey(e.entryId, e.path) === key);
201
+ if (index < 0 || !editors[index].preview) return;
202
+ this._commit(
203
+ editors.map((e, i) => (i === index ? { ...e, preview: false } : e)),
204
+ key,
205
+ );
206
+ }
207
+
208
+ /** Focus an already-open editor. No-op if it is not open. */
209
+ activate(entryId: string, path: string): void {
210
+ const key = editorKey(entryId, path);
211
+ if (!this._snapshot.editors.some((e) => editorKey(e.entryId, e.path) === key)) {
212
+ return;
213
+ }
214
+ this._setActive(key);
215
+ }
216
+
217
+ /**
218
+ * Close an editor. When the closed tab was active, the neighbor to its right
219
+ * (or, failing that, its left) becomes active — matching editor conventions.
220
+ */
221
+ close(entryId: string, path: string): void {
222
+ const key = editorKey(entryId, path);
223
+ const { editors, activeKey } = this._snapshot;
224
+ const index = editors.findIndex((e) => editorKey(e.entryId, e.path) === key);
225
+ if (index < 0) return;
226
+
227
+ const editorsNext = editors.filter((_, i) => i !== index);
228
+
229
+ let nextActiveKey = activeKey;
230
+ if (activeKey === key) {
231
+ const neighbor = editorsNext[index] ?? editorsNext[index - 1] ?? null;
232
+ nextActiveKey = neighbor
233
+ ? editorKey(neighbor.entryId, neighbor.path)
234
+ : null;
235
+ }
236
+ this._commit(editorsNext, nextActiveKey);
237
+ }
238
+
239
+ /** Close every editor. */
240
+ closeAll(): void {
241
+ if (this._snapshot.editors.length === 0) return;
242
+ this._snapshot = EMPTY_SNAPSHOT;
243
+ this._notify();
244
+ }
245
+
246
+ // -- useSyncExternalStore contract ---------------------------------------
247
+
248
+ subscribe = (listener: Listener): (() => void) => {
249
+ this._listeners.add(listener);
250
+ return () => {
251
+ this._listeners.delete(listener);
252
+ };
253
+ };
254
+
255
+ /** Returns the current snapshot (stable ref between mutations). */
256
+ getSnapshot = (): WorkspaceEditorsSnapshot => this._snapshot;
257
+
258
+ // -- Internal ------------------------------------------------------------
259
+
260
+ private _setActive(key: string | null): void {
261
+ if (this._snapshot.activeKey === key) return;
262
+ this._commit(this._snapshot.editors, key);
263
+ }
264
+
265
+ /** Build a reveal for `key` from open options, or `null` when no line given. */
266
+ private _makeReveal(
267
+ key: string,
268
+ options: OpenFileOptions | undefined,
269
+ ): EditorReveal | null {
270
+ const line = options?.line;
271
+ if (line == null) return null;
272
+ return { key, line, nonce: ++this._revealSeq };
273
+ }
274
+
275
+ /**
276
+ * Replace the snapshot. `reveal` defaults to `null`, so any mutation that does
277
+ * not explicitly carry one clears a pending reveal (invariant: returning to a
278
+ * tab must not re-scroll to a stale line). The `activeFile` object reference
279
+ * is preserved when the active editor is unchanged, so a reveal-only update
280
+ * doesn't churn downstream memoization (diff correlation, tree highlight).
281
+ */
282
+ private _commit(
283
+ editors: readonly OpenEditor[],
284
+ activeKey: string | null,
285
+ reveal: EditorReveal | null = null,
286
+ ): void {
287
+ const active =
288
+ activeKey != null
289
+ ? editors.find((e) => editorKey(e.entryId, e.path) === activeKey) ?? null
290
+ : null;
291
+ const resolvedActiveKey = active ? activeKey : null;
292
+
293
+ const prev = this._snapshot;
294
+ const activeFile =
295
+ active && prev.activeKey === resolvedActiveKey && prev.activeFile
296
+ ? prev.activeFile
297
+ : active
298
+ ? { entryId: active.entryId, path: active.path }
299
+ : null;
300
+
301
+ this._snapshot = {
302
+ editors,
303
+ activeKey: resolvedActiveKey,
304
+ activeFile,
305
+ reveal,
306
+ };
307
+ this._notify();
308
+ }
309
+
310
+ private _notify(): void {
311
+ for (const listener of this._listeners) {
312
+ listener();
313
+ }
314
+ }
315
+ }
@@ -0,0 +1,88 @@
1
+ // Which workspace file is open in the viewer. Sibling to SelectionStore (which
2
+ // owns thread-item inspect selection) — the two are orthogonal domains with
3
+ // different lifecycles, so they stay separate stores rather than one fused FSM.
4
+
5
+ /**
6
+ * A file selected for viewing.
7
+ *
8
+ * Keyed by `entryId` (not the entry value): the workspace entry id is stable
9
+ * within a page load but regenerated on reload, and selection is transient UI
10
+ * state anyway (reset on reload). Consumers must resolve the live entry via
11
+ * `workspace.entries.find(e => e.id === sel.entryId)` so a removed entry
12
+ * deselects naturally instead of holding a stale value.
13
+ */
14
+ export interface SelectedWorkspaceFile {
15
+ /** Stable-within-page id of the owning workspace entry. */
16
+ readonly entryId: string;
17
+ /** Repo-relative (git) or root-relative (local) file path. */
18
+ readonly path: string;
19
+ }
20
+
21
+ type Listener = () => void;
22
+
23
+ /**
24
+ * Framework-agnostic store for the currently-open workspace file.
25
+ *
26
+ * Implements the `useSyncExternalStore` contract identical to
27
+ * {@link import("./selection-store.js").SelectionStore}: the context carries the
28
+ * store instance (stable ref), and `getSelection` returns the stored object
29
+ * reference — a new object is allocated only inside `select` — so `getSnapshot`
30
+ * never triggers a re-render loop.
31
+ */
32
+ export class WorkspaceFileSelectionStore {
33
+ private _selected: SelectedWorkspaceFile | null = null;
34
+ private _listeners = new Set<Listener>();
35
+
36
+ // -- Mutations -----------------------------------------------------------
37
+
38
+ select(file: SelectedWorkspaceFile): void {
39
+ if (selectedEqual(this._selected, file)) return;
40
+ this._selected = file;
41
+ this._notify();
42
+ }
43
+
44
+ deselect(): void {
45
+ if (this._selected === null) return;
46
+ this._selected = null;
47
+ this._notify();
48
+ }
49
+
50
+ toggle(file: SelectedWorkspaceFile): void {
51
+ if (selectedEqual(this._selected, file)) {
52
+ this.deselect();
53
+ } else {
54
+ this.select(file);
55
+ }
56
+ }
57
+
58
+ // -- useSyncExternalStore contract ---------------------------------------
59
+
60
+ subscribe = (listener: Listener): (() => void) => {
61
+ this._listeners.add(listener);
62
+ return () => {
63
+ this._listeners.delete(listener);
64
+ };
65
+ };
66
+
67
+ /** Returns the current selection (stable ref between mutations). */
68
+ getSelection = (): SelectedWorkspaceFile | null => {
69
+ return this._selected;
70
+ };
71
+
72
+ // -- Internal ------------------------------------------------------------
73
+
74
+ private _notify(): void {
75
+ for (const listener of this._listeners) {
76
+ listener();
77
+ }
78
+ }
79
+ }
80
+
81
+ function selectedEqual(
82
+ a: SelectedWorkspaceFile | null,
83
+ b: SelectedWorkspaceFile | null,
84
+ ): boolean {
85
+ if (a === b) return true;
86
+ if (a === null || b === null) return false;
87
+ return a.entryId === b.entryId && a.path === b.path;
88
+ }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useRef, type RefObject } from "react";
4
+ import { prefersReducedMotion } from "./motion-preference.js";
5
+
6
+ /**
7
+ * A transient request to scroll a line-faithful code view to a specific line
8
+ * and emphasize it — the "reveal" seam behind jump-to-line navigation (e.g. a
9
+ * content-search hit opening its file at the matched line).
10
+ *
11
+ * `line` is 1-based, matching the gutter and the grep's line numbers. `nonce`
12
+ * is a monotonic token bumped by the producer on every navigation — including a
13
+ * repeat navigation to the same `line` — so an already-mounted view re-scrolls
14
+ * even when the target line is unchanged. The line is deliberately NOT part of
15
+ * an editor's identity (see `WorkspaceEditorsStore`): it is navigation, not
16
+ * state, so it must not key a remount.
17
+ */
18
+ export interface RevealTarget {
19
+ /** 1-based line to scroll to and highlight. */
20
+ readonly line: number;
21
+ /** Monotonic token; a change re-triggers the scroll (even for the same line). */
22
+ readonly nonce: number;
23
+ }
24
+
25
+ /** Return value of {@link useRevealLine}. */
26
+ export interface UseRevealLineResult<T extends HTMLElement = HTMLElement> {
27
+ /**
28
+ * Attach to the element that wraps the addressable line rows. The hook scopes
29
+ * its `[data-line]` query to this subtree, so multiple viewers never collide.
30
+ */
31
+ readonly containerRef: RefObject<T | null>;
32
+ /** Whether a given 1-based line is the current reveal target (for the highlight). */
33
+ readonly isRevealed: (line: number) => boolean;
34
+ }
35
+
36
+ /**
37
+ * Scrolls a line-faithful code view to `reveal.line` and reports which line to
38
+ * highlight.
39
+ *
40
+ * The mechanics are centralized here (rather than duplicated into each line
41
+ * renderer) and mirror the established `data-attribute` scroll pattern used by
42
+ * the workflow waterfall: the consumer renders each row with `data-line="N"`
43
+ * and this hook, on every `reveal.nonce` change, finds the target row within
44
+ * `containerRef` and scrolls it into view. Smooth scroll yields to
45
+ * `prefers-reduced-motion` (a JS animation the CSS rule does not cover), and
46
+ * the scroll is an optional call so it is a no-op under jsdom, which has no
47
+ * `scrollIntoView`.
48
+ *
49
+ * Placement matters: mount this inside the line renderer, which exists only
50
+ * after the file content has loaded — never above the async content gate — so
51
+ * the `[data-line]` rows are present when the effect runs and a fresh open does
52
+ * not race the load. A missing or out-of-range line is a silent no-op.
53
+ */
54
+ export function useRevealLine<T extends HTMLElement = HTMLElement>(
55
+ reveal: RevealTarget | undefined,
56
+ ): UseRevealLineResult<T> {
57
+ const containerRef = useRef<T | null>(null);
58
+
59
+ useEffect(() => {
60
+ if (!reveal) return;
61
+ const container = containerRef.current;
62
+ if (!container) return;
63
+
64
+ const row = container.querySelector(`[data-line="${reveal.line}"]`);
65
+ // Optional call: jsdom (tests) implements no `scrollIntoView`.
66
+ row?.scrollIntoView?.({
67
+ block: "center",
68
+ behavior: prefersReducedMotion() ? "auto" : "smooth",
69
+ });
70
+ // Intentionally keyed only on the nonce: a repeat navigation to the same
71
+ // line (new nonce) must re-scroll, and `reveal.line` changes always ship a
72
+ // new nonce, so it is covered transitively.
73
+ }, [reveal?.nonce]);
74
+
75
+ const isRevealed = useCallback(
76
+ (line: number) => reveal?.line === line,
77
+ [reveal?.line],
78
+ );
79
+
80
+ return { containerRef, isRevealed };
81
+ }
@@ -6,9 +6,12 @@ import {
6
6
  } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
7
7
  import { ExecutionArtifactSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
8
8
  import { ExecutionArtifactKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
9
+ import { ApiResourceMetadataSchema } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
9
10
  import {
10
11
  isPlanArtifact,
12
+ isPlanArtifactName,
11
13
  findPlanArtifact,
14
+ findLatestSessionPlan,
12
15
  PLAN_ARTIFACT_NAME,
13
16
  } from "../detect-plan-artifact";
14
17
 
@@ -26,11 +29,38 @@ function executionWith(...artifacts: ReturnType<typeof artifact>[]) {
26
29
  });
27
30
  }
28
31
 
32
+ function executionWithId(id: string, ...artifacts: ReturnType<typeof artifact>[]) {
33
+ return create(AgentExecutionSchema, {
34
+ metadata: create(ApiResourceMetadataSchema, { id }),
35
+ status: create(AgentExecutionStatusSchema, { artifacts }),
36
+ });
37
+ }
38
+
39
+ describe("isPlanArtifactName", () => {
40
+ it("accepts the legacy exact name and any *.plan.md", () => {
41
+ expect(isPlanArtifactName(PLAN_ARTIFACT_NAME)).toBe(true);
42
+ expect(isPlanArtifactName("feature_x.plan.md")).toBe(true);
43
+ // The runner's current shape: hyphenated slug + content-hash discriminator.
44
+ expect(isPlanArtifactName("fix-the-download-ux_a1b2c3d4.plan.md")).toBe(true);
45
+ expect(isPlanArtifactName("a1b2c3d4.plan.md")).toBe(true);
46
+ });
47
+
48
+ it("rejects non-plan filenames", () => {
49
+ expect(isPlanArtifactName("report.md")).toBe(false);
50
+ expect(isPlanArtifactName("plan.txt")).toBe(false);
51
+ expect(isPlanArtifactName("myplan.md")).toBe(false);
52
+ });
53
+ });
54
+
29
55
  describe("isPlanArtifact", () => {
30
56
  it("is true for a FILE artifact named plan.md", () => {
31
57
  expect(isPlanArtifact(artifact({ name: PLAN_ARTIFACT_NAME }))).toBe(true);
32
58
  });
33
59
 
60
+ it("is true for a FILE artifact named <slug>_<id>.plan.md", () => {
61
+ expect(isPlanArtifact(artifact({ name: "plan-card-ux-cleanup_a1b2c3d4.plan.md" }))).toBe(true);
62
+ });
63
+
34
64
  it("is false for a directory named plan.md", () => {
35
65
  expect(
36
66
  isPlanArtifact(artifact({ name: "plan.md", kind: ExecutionArtifactKind.DIRECTORY })),
@@ -73,3 +103,42 @@ describe("findPlanArtifact", () => {
73
103
  expect(findPlanArtifact(executionWith(artifact({ name: "report.md" })))).toBeUndefined();
74
104
  });
75
105
  });
106
+
107
+ describe("findLatestSessionPlan", () => {
108
+ it("returns undefined when no execution published a plan", () => {
109
+ const execs = [
110
+ executionWithId("e1", artifact({ name: "report.md" })),
111
+ executionWithId("e2"),
112
+ ];
113
+ expect(findLatestSessionPlan(execs)).toBeUndefined();
114
+ expect(findLatestSessionPlan([])).toBeUndefined();
115
+ });
116
+
117
+ it("returns the newest plan across executions, with its execution id", () => {
118
+ const execs = [
119
+ executionWithId("e1", artifact({ name: PLAN_ARTIFACT_NAME, storageKey: "artifacts/e1/plan.md" })),
120
+ executionWithId("e2", artifact({ name: "notes.txt" })),
121
+ executionWithId("e3", artifact({ name: PLAN_ARTIFACT_NAME, storageKey: "artifacts/e3/plan.md" })),
122
+ ];
123
+
124
+ const plan = findLatestSessionPlan(execs);
125
+ expect(plan?.executionId).toBe("e3");
126
+ expect(plan?.artifact.storageKey).toBe("artifacts/e3/plan.md");
127
+ });
128
+
129
+ it("skips a newer plan-less execution and returns the older plan", () => {
130
+ const execs = [
131
+ executionWithId("e1", artifact({ name: PLAN_ARTIFACT_NAME })),
132
+ executionWithId("e2", artifact({ name: "data.json" })),
133
+ ];
134
+ expect(findLatestSessionPlan(execs)?.executionId).toBe("e1");
135
+ });
136
+
137
+ it("skips an execution whose plan artifact lacks a usable execution id", () => {
138
+ const execs = [
139
+ executionWithId("e1", artifact({ name: PLAN_ARTIFACT_NAME })),
140
+ executionWith(artifact({ name: PLAN_ARTIFACT_NAME })),
141
+ ];
142
+ expect(findLatestSessionPlan(execs)?.executionId).toBe("e1");
143
+ });
144
+ });