@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
@@ -58,8 +58,9 @@ import {
58
58
  *
59
59
  * function handleBuildFromPlan() {
60
60
  * // Switch to Agent mode and run immediately — no manual Send.
61
- * composerRef.current?.submit("Implement the plan above", {
61
+ * composerRef.current?.submit("Build from plan", {
62
62
  * interactionMode: "agent",
63
+ * buildFromPlan: true,
63
64
  * });
64
65
  * }
65
66
  *
@@ -83,10 +84,23 @@ export interface SessionComposerHandle {
83
84
  * @param options.interactionMode - Force the interaction mode for this one
84
85
  * submission, overriding the picker. Avoids the same-tick race when the
85
86
  * caller also calls `onInteractionModeChange` just before submitting.
87
+ * @param options.attachments - Pre-uploaded attachments (storage keys from
88
+ * `agentExecution.uploadAttachment`) to include with this one submission,
89
+ * in addition to any files attached in the composer. Used by "Build from
90
+ * plan" to deliver the approved `plan.md` to the implement execution.
91
+ * @param options.buildFromPlan - Marks this submission as the implement
92
+ * turn of a Plan → Build handoff (`execution_config.build_from_plan`).
93
+ * The runner injects the implement-plan directive and the thread hides
94
+ * the turn's message; the message stays a short label for surfaces
95
+ * without that treatment (the CLI, history).
86
96
  */
87
97
  submit(
88
98
  message: string,
89
- options?: { interactionMode?: InteractionModeOption },
99
+ options?: {
100
+ interactionMode?: InteractionModeOption;
101
+ attachments?: AttachmentInput[];
102
+ buildFromPlan?: boolean;
103
+ },
90
104
  ): void;
91
105
  }
92
106
 
@@ -142,6 +156,18 @@ export interface SessionComposerSubmitContext {
142
156
  * Pass to execution creation as `workspaceFileRefs`.
143
157
  */
144
158
  readonly workspaceFileRefs?: string[];
159
+ /**
160
+ * The submission is a Build-from-plan turn.
161
+ *
162
+ * Only ever set through {@link SessionComposerHandle.submit} (the thread
163
+ * card / plan editor CTA) — there is no composer UI for it. Pass to
164
+ * execution creation as `execution_config.build_from_plan`; the runner
165
+ * injects the implement-plan directive and the thread hides the turn's
166
+ * machine-written message (the plan card above it is the visible cause).
167
+ *
168
+ * `undefined` for ordinary submissions.
169
+ */
170
+ readonly buildFromPlan?: boolean;
145
171
  }
146
172
 
147
173
  /** Props for {@link SessionComposer}. */
@@ -724,7 +750,14 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
724
750
  // ---------------------------------------------------------------------------
725
751
 
726
752
  const handleSubmit = useCallback(
727
- async (message: string, modeOverride?: InteractionModeOption) => {
753
+ async (
754
+ message: string,
755
+ overrides?: {
756
+ interactionMode?: InteractionModeOption;
757
+ attachments?: AttachmentInput[];
758
+ buildFromPlan?: boolean;
759
+ },
760
+ ) => {
728
761
  // Persist save-for-future manual secrets before building runtimeEnv
729
762
  if (sessionVariables?.hasSaveForFutureEntries) {
730
763
  const saveVars = sessionVariables.toSaveForFutureEnv();
@@ -763,27 +796,34 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
763
796
  Object.assign(env, sessionVariables.toRuntimeEnv());
764
797
  }
765
798
 
766
- const attachmentInputs = enableAttachments
799
+ // Composer-attached files plus caller-supplied extras (e.g. the approved
800
+ // plan.md from a Build-from-plan turn) travel as one attachment list.
801
+ const composerAttachments = enableAttachments
767
802
  ? attachments.toAttachmentInputs()
768
- : undefined;
803
+ : [];
804
+ const attachmentInputs = [
805
+ ...composerAttachments,
806
+ ...(overrides?.attachments ?? []),
807
+ ];
769
808
 
770
809
  const hasEnv = Object.keys(env).length > 0;
771
- const hasAttachments =
772
- attachmentInputs !== undefined && attachmentInputs.length > 0;
810
+ const hasAttachments = attachmentInputs.length > 0;
773
811
  const effectiveMode =
774
- modeOverride ??
812
+ overrides?.interactionMode ??
775
813
  (showInteractionModePicker && interactionMode
776
814
  ? interactionMode
777
815
  : undefined);
778
816
  const hasFileRefs = enableFileReferences && fileRefs.hasRefs;
817
+ const buildFromPlan = overrides?.buildFromPlan;
779
818
 
780
819
  const context: SessionComposerSubmitContext | undefined =
781
- hasEnv || hasAttachments || effectiveMode || hasFileRefs
820
+ hasEnv || hasAttachments || effectiveMode || hasFileRefs || buildFromPlan
782
821
  ? {
783
822
  runtimeEnv: hasEnv ? env : undefined,
784
823
  attachments: hasAttachments ? attachmentInputs : undefined,
785
824
  interactionMode: effectiveMode,
786
825
  workspaceFileRefs: hasFileRefs ? [...fileRefs.refs] : undefined,
826
+ buildFromPlan,
787
827
  }
788
828
  : undefined;
789
829
 
@@ -813,7 +853,7 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
813
853
  submit: (message, options) => {
814
854
  const trimmed = message.trim();
815
855
  if (!trimmed || isDisabled) return;
816
- void handleSubmit(trimmed, options?.interactionMode);
856
+ void handleSubmit(trimmed, options);
817
857
  },
818
858
  }), [composer.setMessage, composer.textareaRef, handleSubmit, isDisabled]);
819
859
 
@@ -1343,6 +1383,9 @@ const SessionComposerInner = forwardRef<SessionComposerHandle, SessionComposerPr
1343
1383
  className={cn(
1344
1384
  "rounded-xl border border-border bg-card shadow-sm",
1345
1385
  "focus-within:ring-2 focus-within:ring-ring",
1386
+ // Plan mode tints the frame so the active mode is visible at a
1387
+ // glance, without opening the picker. Themeable via --stgm-primary-muted.
1388
+ interactionMode === "plan" && "border-primary-muted",
1346
1389
  isDisabled && !stopMode && "opacity-50",
1347
1390
  isDragOver && "ring-2 ring-primary/50",
1348
1391
  )}
@@ -125,7 +125,7 @@ describe("SessionComposer — public contract", () => {
125
125
  });
126
126
  });
127
127
 
128
- describe("SessionComposer — imperative submit (Implement plan)", () => {
128
+ describe("SessionComposer — imperative submit (Build from plan)", () => {
129
129
  it("submits a message with the interactionMode override, even when the picker is 'plan'", async () => {
130
130
  const ref = createRef<SessionComposerHandle>();
131
131
  const { onSubmit } = renderComposer({
@@ -134,13 +134,13 @@ describe("SessionComposer — imperative submit (Implement plan)", () => {
134
134
  interactionMode: "plan",
135
135
  });
136
136
 
137
- ref.current!.submit("Implement the plan above", { interactionMode: "agent" });
137
+ ref.current!.submit("Build from plan", { interactionMode: "agent" });
138
138
 
139
139
  await waitFor(() => {
140
140
  expect(onSubmit).toHaveBeenCalledOnce();
141
141
  });
142
142
  const [message, , context] = onSubmit.mock.calls[0];
143
- expect(message).toBe("Implement the plan above");
143
+ expect(message).toBe("Build from plan");
144
144
  // The override must win over the current picker value ("plan") so the
145
145
  // implement run executes in Agent mode regardless of render timing.
146
146
  expect(context?.interactionMode).toBe("agent");
@@ -150,7 +150,7 @@ describe("SessionComposer — imperative submit (Implement plan)", () => {
150
150
  const ref = createRef<SessionComposerHandle>();
151
151
  const { onSubmit } = renderComposer({ ref, disabled: true });
152
152
 
153
- ref.current!.submit("Implement the plan above", { interactionMode: "agent" });
153
+ ref.current!.submit("Build from plan", { interactionMode: "agent" });
154
154
 
155
155
  expect(onSubmit).not.toHaveBeenCalled();
156
156
  });
@@ -163,4 +163,58 @@ describe("SessionComposer — imperative submit (Implement plan)", () => {
163
163
 
164
164
  expect(onSubmit).not.toHaveBeenCalled();
165
165
  });
166
+
167
+ it("carries caller-supplied attachments into the submit context (approved plan.md)", async () => {
168
+ const ref = createRef<SessionComposerHandle>();
169
+ const { onSubmit } = renderComposer({ ref });
170
+
171
+ const planAttachment = {
172
+ filename: "plan.md",
173
+ storageKey: "attachments/01ABC/plan.md",
174
+ mountPath: ".stigmer/inputs/plan.md",
175
+ contentType: "text/markdown",
176
+ };
177
+ ref.current!.submit("Build from plan", {
178
+ interactionMode: "agent",
179
+ attachments: [planAttachment],
180
+ });
181
+
182
+ await waitFor(() => {
183
+ expect(onSubmit).toHaveBeenCalledOnce();
184
+ });
185
+ const [, , context] = onSubmit.mock.calls[0];
186
+ expect(context?.attachments).toEqual([planAttachment]);
187
+ expect(context?.interactionMode).toBe("agent");
188
+ });
189
+
190
+ it("carries the buildFromPlan flag into the submit context", async () => {
191
+ const ref = createRef<SessionComposerHandle>();
192
+ const { onSubmit } = renderComposer({ ref });
193
+
194
+ ref.current!.submit("Build from plan", {
195
+ interactionMode: "agent",
196
+ buildFromPlan: true,
197
+ });
198
+
199
+ await waitFor(() => {
200
+ expect(onSubmit).toHaveBeenCalledOnce();
201
+ });
202
+ const [, , context] = onSubmit.mock.calls[0];
203
+ expect(context?.buildFromPlan).toBe(true);
204
+ expect(context?.interactionMode).toBe("agent");
205
+ });
206
+
207
+ it("leaves buildFromPlan unset for ordinary submissions", async () => {
208
+ const { onSubmit } = renderComposer();
209
+
210
+ const textarea = screen.getByRole("textbox");
211
+ fireEvent.change(textarea, { target: { value: "Hello world" } });
212
+ fireEvent.keyDown(textarea, { key: "Enter", shiftKey: false });
213
+
214
+ await waitFor(() => {
215
+ expect(onSubmit).toHaveBeenCalledOnce();
216
+ });
217
+ const [, , context] = onSubmit.mock.calls[0];
218
+ expect(context?.buildFromPlan).toBeUndefined();
219
+ });
166
220
  });
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useMemo, useState, type ReactNode } from "react";
3
+ import { useEffect, useMemo, useState, type ReactNode } from "react";
4
4
  import Markdown from "react-markdown";
5
5
  import { cn } from "@stigmer/theme";
6
6
  import {
@@ -8,6 +8,7 @@ import {
8
8
  REMARK_PLUGINS,
9
9
  unwrapEnclosingMarkdownFence,
10
10
  } from "../internal/markdown-components.js";
11
+ import { useRevealLine, type RevealTarget } from "../internal/useRevealLine.js";
11
12
  import {
12
13
  getArtifactRenderMode,
13
14
  type ArtifactRenderMode,
@@ -29,6 +30,15 @@ export interface ArtifactContentRendererProps {
29
30
  readonly contentType?: string | null;
30
31
  /** When `true`, shows a truncation warning below the content. */
31
32
  readonly isTruncated?: boolean;
33
+ /**
34
+ * Optional jump-to-line request. When set, the content is rendered
35
+ * **line-faithfully** so the line number maps to the file's real rows —
36
+ * Markdown defaults to its Source view (a line only exists in source) and
37
+ * JSON renders raw rather than pretty-printed (reformatting would shift line
38
+ * numbers) — and the target line is scrolled into view and highlighted. Absent
39
+ * (the default), rendering is unchanged. See {@link RevealTarget}.
40
+ */
41
+ readonly reveal?: RevealTarget;
32
42
  /** Additional CSS classes for the root element. */
33
43
  readonly className?: string;
34
44
  }
@@ -76,6 +86,7 @@ export function ArtifactContentRenderer({
76
86
  fileName,
77
87
  contentType,
78
88
  isTruncated = false,
89
+ reveal,
79
90
  className,
80
91
  }: ArtifactContentRendererProps) {
81
92
  const mode = getArtifactRenderMode(fileName, contentType);
@@ -83,13 +94,19 @@ export function ArtifactContentRenderer({
83
94
  return (
84
95
  <div className={cn(className)}>
85
96
  {mode === "markdown" ? (
86
- <MarkdownView content={content} />
97
+ <MarkdownView content={content} reveal={reveal} />
87
98
  ) : mode === "yaml" ? (
88
- <YamlView content={content} />
99
+ <YamlView content={content} reveal={reveal} />
89
100
  ) : mode === "json" ? (
90
- <JsonView content={content} />
101
+ // A reveal needs raw, line-faithful JSON: pretty-printing reformats the
102
+ // file and would shift the target line off its real row.
103
+ reveal ? (
104
+ <LineNumberedPre content={content} reveal={reveal} />
105
+ ) : (
106
+ <JsonView content={content} />
107
+ )
91
108
  ) : (
92
- <PlainTextView content={content} />
109
+ <PlainTextView content={content} reveal={reveal} />
93
110
  )}
94
111
 
95
112
  {isTruncated && <TruncationWarning />}
@@ -103,8 +120,21 @@ export function ArtifactContentRenderer({
103
120
 
104
121
  type MarkdownTab = "rendered" | "source";
105
122
 
106
- function MarkdownView({ content }: { readonly content: string }) {
107
- const [tab, setTab] = useState<MarkdownTab>("rendered");
123
+ function MarkdownView({
124
+ content,
125
+ reveal,
126
+ }: {
127
+ readonly content: string;
128
+ readonly reveal?: RevealTarget;
129
+ }) {
130
+ // A reveal targets a source line (the rendered HTML has no line concept), so
131
+ // default to — and, on every new reveal, return to — the Source view.
132
+ const [tab, setTab] = useState<MarkdownTab>(reveal ? "source" : "rendered");
133
+ // Keyed on the reveal nonce: each fresh jump-to-line returns to Source (where
134
+ // the line exists); switching to Rendered afterward sticks until the next hit.
135
+ useEffect(() => {
136
+ if (reveal) setTab("source");
137
+ }, [reveal?.nonce]);
108
138
 
109
139
  // A `.md` whose entire body is one ```markdown fence (e.g. a model-wrapped
110
140
  // plan) would render flat. Unwrap for the Rendered view only — Source stays
@@ -145,7 +175,7 @@ function MarkdownView({ content }: { readonly content: string }) {
145
175
  </Markdown>
146
176
  </div>
147
177
  ) : (
148
- <LineNumberedPre content={content} />
178
+ <LineNumberedPre content={content} reveal={reveal} />
149
179
  )}
150
180
  </div>
151
181
  );
@@ -197,15 +227,6 @@ function TabButton({
197
227
  *
198
228
  * Values and block scalar content render in the default `text-foreground`.
199
229
  */
200
- function highlightYaml(content: string): ReactNode {
201
- return content.split("\n").map((line, i) => (
202
- <span key={i}>
203
- {i > 0 && "\n"}
204
- {highlightYamlLine(line)}
205
- </span>
206
- ));
207
- }
208
-
209
230
  function highlightYamlLine(line: string): ReactNode {
210
231
  if (!line.trim()) return line;
211
232
 
@@ -253,10 +274,38 @@ function highlightYamlValue(value: string): ReactNode {
253
274
  return <>{value}</>;
254
275
  }
255
276
 
256
- function YamlView({ content }: { readonly content: string }) {
277
+ function YamlView({
278
+ content,
279
+ reveal,
280
+ }: {
281
+ readonly content: string;
282
+ readonly reveal?: RevealTarget;
283
+ }) {
284
+ const { containerRef, isRevealed } = useRevealLine(reveal);
285
+ const lines = content.split("\n");
286
+
257
287
  return (
258
288
  <pre className="overflow-x-auto p-4 font-mono text-xs leading-relaxed text-foreground">
259
- <code>{highlightYaml(content)}</code>
289
+ <code ref={containerRef}>
290
+ {lines.map((line, i) => (
291
+ <span
292
+ key={i}
293
+ data-line={i + 1}
294
+ className={cn(
295
+ // Transparent left border by default so revealing a line only
296
+ // changes its color — never shifts the text (no layout jump).
297
+ "block border-l-2 border-transparent",
298
+ isRevealed(i + 1) && "border-primary bg-primary-subtle",
299
+ )}
300
+ >
301
+ {/* whitespace-pre inner + `\n` fallback preserves indentation and
302
+ the height of blank lines, mirroring LineNumberedPre. */}
303
+ <span className="whitespace-pre">
304
+ {line.trim() ? highlightYamlLine(line) : "\n"}
305
+ </span>
306
+ </span>
307
+ ))}
308
+ </code>
260
309
  </pre>
261
310
  );
262
311
  }
@@ -345,19 +394,47 @@ function JsonView({ content }: { readonly content: string }) {
345
394
  // Plain text view (monospace + line numbers)
346
395
  // ---------------------------------------------------------------------------
347
396
 
348
- function PlainTextView({ content }: { readonly content: string }) {
349
- return <LineNumberedPre content={content} />;
397
+ function PlainTextView({
398
+ content,
399
+ reveal,
400
+ }: {
401
+ readonly content: string;
402
+ readonly reveal?: RevealTarget;
403
+ }) {
404
+ return <LineNumberedPre content={content} reveal={reveal} />;
350
405
  }
351
406
 
352
- function LineNumberedPre({ content }: { readonly content: string }) {
407
+ /**
408
+ * Line-numbered monospace view. The single line-faithful primitive behind plain
409
+ * text, Markdown Source, and reveal-mode JSON: every row is addressable
410
+ * (`data-line`), so a {@link RevealTarget} can scroll to and highlight a line
411
+ * (via {@link useRevealLine}). Absent a reveal it renders exactly as before.
412
+ */
413
+ function LineNumberedPre({
414
+ content,
415
+ reveal,
416
+ }: {
417
+ readonly content: string;
418
+ readonly reveal?: RevealTarget;
419
+ }) {
420
+ const { containerRef, isRevealed } = useRevealLine(reveal);
353
421
  const lines = content.split("\n");
354
422
  const gutterWidth = String(lines.length).length;
355
423
 
356
424
  return (
357
425
  <pre className="overflow-x-auto font-mono text-xs leading-relaxed text-foreground">
358
- <code>
426
+ <code ref={containerRef}>
359
427
  {lines.map((line, i) => (
360
- <span key={i} className="flex">
428
+ <span
429
+ key={i}
430
+ data-line={i + 1}
431
+ className={cn(
432
+ // Transparent left border by default so a reveal only recolors the
433
+ // row — never shifts its text (no layout jump).
434
+ "flex border-l-2 border-transparent",
435
+ isRevealed(i + 1) && "border-primary bg-primary-subtle",
436
+ )}
437
+ >
361
438
  <span
362
439
  className="sticky left-0 select-none bg-background pr-4 text-right text-muted-foreground"
363
440
  style={{ minWidth: `${gutterWidth + 2}ch` }}
@@ -47,10 +47,10 @@ export interface ArtifactPreviewContentProps {
47
47
  */
48
48
  readonly onApplied?: (result: ApplyResourceResult) => void;
49
49
  /**
50
- * Optional plan-build action. When provided, a "Build from plan" primary
51
- * button appears in the action bar (used for plan artifacts: turn the
52
- * plan into an Agent run). Clicking it calls `onImplement` then closes
53
- * the modal. Omit for non-actionable artifacts.
50
+ * Optional plan-build action. When provided, a "Build" primary button
51
+ * appears in the action bar (used for plan artifacts: turn the plan into
52
+ * an Agent run). Clicking it calls `onImplement` then closes the modal.
53
+ * Omit for non-actionable artifacts.
54
54
  */
55
55
  readonly onImplement?: () => void;
56
56
  /** Additional CSS classes for the root container. */
@@ -228,9 +228,9 @@ export function ArtifactPreviewContent({
228
228
  ctaLabel = `Push Skill to ${org}`;
229
229
  }
230
230
 
231
- // "Build from plan" runs the plan; closing the modal lets the host's submit
232
- // pipeline take over (switch to Agent + send). Single combined handler keeps
233
- // the caller's contract simple ("just give me onImplement").
231
+ // "Build" runs the plan; closing the modal lets the host's submit pipeline
232
+ // take over (switch to Agent + send). Single combined handler keeps the
233
+ // caller's contract simple ("just give me onImplement").
234
234
  const handleImplement = useCallback(() => {
235
235
  onImplement?.();
236
236
  onClose();
@@ -329,7 +329,7 @@ export interface ArtifactPreviewModalProps {
329
329
  readonly onApplied?: (result: ApplyResourceResult) => void;
330
330
  /**
331
331
  * Optional plan-build action (see {@link ArtifactPreviewContentProps.onImplement}).
332
- * When provided, a "Build from plan" primary button appears; clicking it calls
332
+ * When provided, a "Build" primary button appears; clicking it calls
333
333
  * `onImplement` then closes the modal.
334
334
  */
335
335
  readonly onImplement?: () => void;
@@ -701,7 +701,7 @@ function ActionBar({
701
701
  )}
702
702
  >
703
703
  <ImplementIcon />
704
- Build from plan
704
+ Build
705
705
  </button>
706
706
  )}
707
707
  {applyResult ? (
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import { memo, useState } from "react";
4
+ import type { FileChangeProgress } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
5
+ import { cn } from "@stigmer/theme";
6
+ import { FileKindBadge, FileLineStats } from "./FileReviewAtoms.js";
7
+ import { FilePathLink } from "./FilePathLink.js";
8
+
9
+ /** Props for {@link FileChangeProgressBar}. */
10
+ export interface FileChangeProgressBarProps {
11
+ /**
12
+ * The transient mid-run progress snapshot for the active turn (DD-32). Feed
13
+ * this from `useSessionConversation().fileChangeProgress`. Renders nothing when
14
+ * undefined or when no files have changed yet — the server clears it once the
15
+ * turn's change set leaves CAPTURING, so the bar disappears exactly when the
16
+ * decision surface ({@link FileReviewDock}) takes over.
17
+ */
18
+ readonly progress: FileChangeProgress | undefined;
19
+ /** Additional CSS class names for the root container. */
20
+ readonly className?: string;
21
+ }
22
+
23
+ /**
24
+ * The live "N files changed so far" strip for a turn that is still running.
25
+ *
26
+ * WHY IT EXISTS. The transcript streams per-edit tool rows, but a change made by
27
+ * a shell command (a `sed`, a build step) has no tool row, and there is no
28
+ * running net-of-many-edits rollup until the turn ends. This bar fills that gap:
29
+ * a compact, NON-INTERACTIVE count of the workspace delta accumulating during
30
+ * the turn, pinned above the composer where the {@link FileReviewDock} will later
31
+ * appear. The two are mutually exclusive per turn (progress shows while CAPTURING;
32
+ * the dock shows once AWAITING_REVIEW), so the bar hands off cleanly to the dock.
33
+ *
34
+ * NEVER A DECISION SURFACE. Progress is non-authoritative and carries no file
35
+ * bodies — it is never reviewable or decidable. The reviewed diff is the
36
+ * turn-boundary candidate ({@link FileReviewDock} / the stamped transcript rows).
37
+ * A secret-like path is surfaced by name with its counts withheld (zeroed), so
38
+ * `FileLineStats` renders nothing for it.
39
+ *
40
+ * A11Y. Only the summary count is an `aria-live` region — the per-file list is
41
+ * not — so a screen reader hears "3 files changing" as the count moves, not a
42
+ * per-file barrage on every ~2s refresh.
43
+ *
44
+ * Purely presentational (headless-first, DD-003): the data lives in
45
+ * `useSessionConversation().fileChangeProgress`. `SessionViewer` mounts it above
46
+ * the dock by default.
47
+ *
48
+ * @example
49
+ * ```tsx
50
+ * const conv = useSessionConversation(sessionId);
51
+ * <FileChangeProgressBar progress={conv.fileChangeProgress} />
52
+ * ```
53
+ */
54
+ export const FileChangeProgressBar = memo(function FileChangeProgressBar({
55
+ progress,
56
+ className,
57
+ }: FileChangeProgressBarProps) {
58
+ const [expanded, setExpanded] = useState(false);
59
+
60
+ if (!progress || progress.filesChanged === 0) return null;
61
+
62
+ const { filesChanged, linesAdded, linesRemoved, entries } = progress;
63
+ const hiddenCount = filesChanged - entries.length;
64
+
65
+ return (
66
+ <div
67
+ role="region"
68
+ aria-label="File changes in progress"
69
+ data-cursor-target="file-change-progress-bar"
70
+ className={cn("border-t border-border-muted px-4 py-2", className)}
71
+ >
72
+ <div className="flex items-center gap-2">
73
+ <button
74
+ type="button"
75
+ onClick={() => setExpanded((prev) => !prev)}
76
+ aria-expanded={expanded}
77
+ className="flex min-w-0 flex-1 items-center gap-2 text-left text-xs text-muted-foreground hover:text-foreground"
78
+ >
79
+ <span
80
+ aria-hidden="true"
81
+ className={cn("shrink-0 transition-transform", expanded && "rotate-90")}
82
+ >
83
+
84
+ </span>
85
+ {/* Only the count is live: a screen reader hears the aggregate move,
86
+ not a per-file announcement on every refresh. */}
87
+ <span aria-live="polite" className="min-w-0 truncate">
88
+ {filesChanged} {filesChanged === 1 ? "file" : "files"} changing…
89
+ </span>
90
+ </button>
91
+ <FileLineStats linesAdded={linesAdded} linesRemoved={linesRemoved} />
92
+ </div>
93
+ {expanded && (
94
+ <ul className="mt-2 max-h-[30vh] space-y-1 overflow-y-auto">
95
+ {entries.map((entry) => {
96
+ const path = entry.pathAfter || entry.pathBefore;
97
+ return (
98
+ <li key={path} className="flex items-center gap-2">
99
+ <FileKindBadge kind={entry.kind} />
100
+ <FilePathLink
101
+ path={path}
102
+ dirDisplay="dim"
103
+ className="min-w-0 flex-1 text-xs"
104
+ />
105
+ <FileLineStats
106
+ linesAdded={entry.linesAdded}
107
+ linesRemoved={entry.linesRemoved}
108
+ />
109
+ </li>
110
+ );
111
+ })}
112
+ {hiddenCount > 0 && (
113
+ <li className="text-xs text-muted-foreground-faint">
114
+ … and {hiddenCount} more
115
+ </li>
116
+ )}
117
+ </ul>
118
+ )}
119
+ </div>
120
+ );
121
+ });
@@ -167,7 +167,13 @@ export function FileChangeDiff({
167
167
  if (change.captureLevel === FileChangeCaptureLevel.HUNK_ONLY) {
168
168
  return { additions: change.linesAdded, deletions: change.linesRemoved };
169
169
  }
170
- return countHunks(hunks);
170
+ // Whole-file: count the rendered hunks (what the user actually sees). When
171
+ // there are none to count — the body is offloaded/loading/truncated — fall
172
+ // back to the capture-time counts, which are computed with the same diff
173
+ // algorithm and so agree with the hunks whenever both exist.
174
+ const computed = countHunks(hunks);
175
+ if (computed.additions > 0 || computed.deletions > 0) return computed;
176
+ return { additions: change.linesAdded, deletions: change.linesRemoved };
171
177
  }, [change.captureLevel, change.linesAdded, change.linesRemoved, hunks]);
172
178
 
173
179
  const showStatsRow = showStats && (stats.additions > 0 || stats.deletions > 0);
@@ -343,8 +349,11 @@ function countHunks(hunks: readonly DiffHunk[]): {
343
349
  * Projects a {@link FileChange} into a {@link FileDiffEntry} for the file list.
344
350
  *
345
351
  * Counts: hunk-only uses the runner's authoritative numbers; whole-file inline
346
- * is derived from the content; an offloaded whole-file side has no synchronous
347
- * content, so its count stays 0 until the file is opened and fetched.
352
+ * is derived from the content (identical to the runner's capture-time counts by
353
+ * construction same diff algorithm). A whole-file side with no synchronous
354
+ * content (offloaded / binary) falls back to the runner's capture-time
355
+ * `linesAdded`/`linesRemoved` — zero when no count exists, which the list
356
+ * renders as no stat.
348
357
  */
349
358
  function toFileDiffEntry(change: FileChange): FileDiffEntry {
350
359
  const changeType =
@@ -370,7 +379,12 @@ function toFileDiffEntry(change: FileChange): FileDiffEntry {
370
379
  return { path: change.path, changeType, additions, deletions };
371
380
  }
372
381
 
373
- return { path: change.path, changeType, additions: 0, deletions: 0 };
382
+ return {
383
+ path: change.path,
384
+ changeType,
385
+ additions: change.linesAdded,
386
+ deletions: change.linesRemoved,
387
+ };
374
388
  }
375
389
 
376
390
  /** Inline text of a side, or null when absent/offloaded/binary. */
@@ -18,11 +18,18 @@ export interface FilePathContextValue {
18
18
  * `FilePathLink` delegates to this callback instead of opening
19
19
  * a URL or copying to clipboard. Platform builders use this to
20
20
  * integrate their own file viewer or navigation.
21
+ *
22
+ * Return `false` to **decline** a specific path — `FilePathLink` then falls
23
+ * back to its default action (open the GitHub link / copy the path), so a
24
+ * handler that can only open *some* paths (e.g. resolvable workspace files)
25
+ * leaves the rest behaving as before. Returning `true` or `void` means the
26
+ * click was fully handled and the default is suppressed — preserving the
27
+ * behavior of existing `void`-returning handlers.
21
28
  */
22
29
  readonly onFilePathClick?: (
23
30
  path: string,
24
31
  resolved: ResolvedPathAction,
25
- ) => void;
32
+ ) => boolean | void;
26
33
  }
27
34
 
28
35
  const DEFAULT_VALUE: FilePathContextValue = {