@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,839 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useId,
6
+ useRef,
7
+ useState,
8
+ type KeyboardEvent,
9
+ type ReactNode,
10
+ } from "react";
11
+ import { cn } from "@stigmer/theme";
12
+ import type { FileChange } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
13
+ import { ResizableSplit } from "../internal/ResizableSplit.js";
14
+ import {
15
+ editorKey,
16
+ type OpenEditor,
17
+ type OpenFileOptions,
18
+ type SelectedWorkspaceFile,
19
+ } from "../internal/store/index.js";
20
+ import type { RevealTarget } from "../internal/useRevealLine.js";
21
+ import type { WorkspaceEntry } from "./useWorkspaceEntries.js";
22
+ import type { WorkspaceFileLister } from "./WorkspaceFileLister.js";
23
+ import type { WorkspaceFileReader } from "./WorkspaceFileReader.js";
24
+ import type { WorkspaceContentSearcher } from "./WorkspaceContentSearcher.js";
25
+ import { WorkspaceFileSearch } from "./WorkspaceFileSearch.js";
26
+ import { WorkspaceContentSearch } from "./WorkspaceContentSearch.js";
27
+ import { FileViewer, type FileViewerHandle } from "./FileViewer.js";
28
+ import { EditorTabs, editorTabDomId } from "./EditorTabs.js";
29
+ import { ExplorerTree } from "./ExplorerTree.js";
30
+
31
+ /** The built-in rail views every surface has. */
32
+ const BUILT_IN_VIEWS = ["files", "search"] as const;
33
+
34
+ /**
35
+ * A host-injected rail view: an icon in the activity rail whose content
36
+ * renders in the sidebar pane when active.
37
+ *
38
+ * This is how session facets (Config, Changes, Artifacts, …) join the built-in
39
+ * Explorer/Search views without the surface knowing any session domain — the
40
+ * same composition philosophy as the injected lister/reader capabilities
41
+ * (DD-004). The surface stays an embeddable, domain-pure workspace organism;
42
+ * hosts extend its rail.
43
+ */
44
+ export interface SurfaceRailView {
45
+ /** Unique view id. Must not collide with the built-in `"files"`/`"search"`. */
46
+ readonly id: string;
47
+ /** Accessible label; also rendered as the sidebar heading. */
48
+ readonly label: string;
49
+ /** Monochrome rail icon (tinted via `currentColor`, DD-005). */
50
+ readonly icon: ReactNode;
51
+ /** Optional count badge rendered over the rail icon (hidden when 0). */
52
+ readonly badge?: number;
53
+ /** Sidebar content rendered while this view is active. */
54
+ readonly content: ReactNode;
55
+ }
56
+
57
+ /**
58
+ * A host-injected virtual document: an editor tab whose content is not a
59
+ * workspace file — the session's `plan.md` today; execution artifacts are the
60
+ * anticipated next family.
61
+ *
62
+ * The editor-area counterpart of {@link SurfaceRailView}'s rail injection
63
+ * (DD-004): virtual documents share the tab group with file tabs (identical
64
+ * open / pin / close / activate semantics through the same editors store),
65
+ * and only the *body* rendering diverges — the surface renders `content`
66
+ * instead of breadcrumbs + `FileViewer`. The surface stays domain-pure: it
67
+ * matches tabs to virtual documents by identity and knows nothing about what
68
+ * the document is.
69
+ *
70
+ * `entryId` must come from `virtualEntryId(kind)` so it can never alias a
71
+ * real workspace entry; `path` doubles as the tab label's source (its
72
+ * basename), matching file-tab behavior.
73
+ */
74
+ export interface SurfaceVirtualDocument {
75
+ /** Virtual entry id (from `virtualEntryId`) — the tab's owning "entry". */
76
+ readonly entryId: string;
77
+ /** Tab identity within the entry; its basename is the tab label. */
78
+ readonly path: string;
79
+ /** Editor-area content rendered while this document's tab is active. */
80
+ readonly content: ReactNode;
81
+ }
82
+
83
+ /** Props for {@link WorkspaceSurface}. */
84
+ export interface WorkspaceSurfaceProps {
85
+ /** The workspace entries whose files are browsable (multi-root). */
86
+ readonly entries: readonly WorkspaceEntry[];
87
+ /** Platform-injected file lister. `undefined` disables explorer + search. */
88
+ readonly lister: WorkspaceFileLister | undefined;
89
+ /** Platform-injected content reader for the editor. */
90
+ readonly reader: WorkspaceFileReader | undefined;
91
+ /**
92
+ * Platform-injected content (text) searcher. When provided, the Search pane
93
+ * gains a `Name | Text` toggle offering full-text search; `undefined` (web/git
94
+ * today) keeps the Search pane filename-only (DD-09). Mirrors the lister's
95
+ * null contract — the honest "unavailable here" state.
96
+ */
97
+ readonly searcher?: WorkspaceContentSearcher;
98
+ /**
99
+ * The active rail view id (`"files"`, `"search"`, or an {@link SurfaceRailView.id}).
100
+ * Provide together with `onViewChange` to control the rail from the host;
101
+ * omit for internal (uncontrolled) view state. An id that matches no view
102
+ * (e.g. a contextual extra view that disappeared) falls back to `"files"`.
103
+ */
104
+ readonly view?: string;
105
+ /** Called when the user picks a rail view. */
106
+ readonly onViewChange?: (viewId: string) => void;
107
+ /**
108
+ * Host-injected rail views, rendered after the built-in Explorer/Search.
109
+ * Their `content` renders in the sidebar pane; the editor area stays for
110
+ * files (the VS Code model).
111
+ */
112
+ readonly extraViews?: readonly SurfaceRailView[];
113
+ /**
114
+ * Host-injected virtual documents (see {@link SurfaceVirtualDocument}).
115
+ * A tab whose identity matches one of these renders its `content` in the
116
+ * editor area instead of the file viewer. The host opens/activates the tab
117
+ * through the same editors-store seams as files.
118
+ */
119
+ readonly virtualDocuments?: readonly SurfaceVirtualDocument[];
120
+ /**
121
+ * Detach a workspace entry. When provided, explorer root headers carry a
122
+ * remove control (VS Code's "Remove Folder from Workspace").
123
+ */
124
+ readonly onRemoveEntry?: (entryId: string) => void;
125
+ /**
126
+ * Attach a local folder (host opens its native picker and adds the entry).
127
+ * When provided, the explorer renders an "Add Folder" footer action —
128
+ * desktop hosts wire this; web hosts omit it (no native picker).
129
+ */
130
+ readonly onAddLocalFolder?: () => void;
131
+ /** Open editor tabs in order (VS Code open-editors model). */
132
+ readonly editors: readonly OpenEditor[];
133
+ /** The active editor's file, or `null`. Highlighted in the tree and shown. */
134
+ readonly selectedFile: SelectedWorkspaceFile | null;
135
+ /**
136
+ * Opens a file as a preview (single click) from the explorer or search. The
137
+ * same seam the tree/search uses; the owner routes it to the editors store.
138
+ * `options.line` (content-search hits) requests a jump-to-line reveal.
139
+ */
140
+ readonly onOpenFile: (
141
+ entryId: string,
142
+ path: string,
143
+ options?: OpenFileOptions,
144
+ ) => void;
145
+ /** Focuses an already-open editor (tab single click). */
146
+ readonly onActivateEditor: (entryId: string, path: string) => void;
147
+ /** Pins an editor (tab double-click) so it stops being the preview tab. */
148
+ readonly onPinEditor: (entryId: string, path: string) => void;
149
+ /** Closes an editor (tab close control or middle click). */
150
+ readonly onCloseEditor: (entryId: string, path: string) => void;
151
+ /** Collapses the surface back to the chat-dominant layout. */
152
+ readonly onCollapse: () => void;
153
+ /**
154
+ * The session {@link FileChange} that touched the open file, when it was
155
+ * changed this session. When present the editor defaults to the authoritative
156
+ * `baseline→candidate` diff (DD-06), matching the inspector's Viewer tab.
157
+ */
158
+ readonly change?: FileChange;
159
+ /**
160
+ * Jump-to-line reveal for the **active** editor (already scoped by the owner
161
+ * to `reveal.key === activeKey`), or `undefined`. Forwarded to the editor's
162
+ * viewer, which opens in File view and scrolls to / highlights the line.
163
+ */
164
+ readonly reveal?: RevealTarget;
165
+ /** Additional CSS classes for the root element. */
166
+ readonly className?: string;
167
+ }
168
+
169
+ /**
170
+ * A VS Code / Cursor-inspired read-only workspace surface: a slim activity rail
171
+ * (Explorer / Search), a resizable sidebar, and a large editor area.
172
+ *
173
+ * This is the workspace-dominant counterpart to the inspector's cramped Viewer
174
+ * tab. The session viewer flips its layout to give this surface the majority of
175
+ * the screen while chat becomes a narrow column (see `SessionViewer`). It is a
176
+ * self-contained organism: given `entries`, a `lister`, a `reader`, and the
177
+ * open-file selection, it renders identically inside the Console or embedded in
178
+ * a third-party host (DD-004). Explicitly read-only — no editing, no commit UI.
179
+ *
180
+ * All visual properties flow through `--stgm-*` tokens (DD-005).
181
+ */
182
+ export function WorkspaceSurface({
183
+ entries,
184
+ lister,
185
+ reader,
186
+ searcher,
187
+ view,
188
+ onViewChange,
189
+ extraViews,
190
+ virtualDocuments,
191
+ onRemoveEntry,
192
+ onAddLocalFolder,
193
+ editors,
194
+ selectedFile,
195
+ onOpenFile,
196
+ onActivateEditor,
197
+ onPinEditor,
198
+ onCloseEditor,
199
+ onCollapse,
200
+ change,
201
+ reveal,
202
+ className,
203
+ }: WorkspaceSurfaceProps) {
204
+ // Optionally controlled (standard React pattern): a host that owns view
205
+ // routing (e.g. the session panel's auto-view logic) passes `view` +
206
+ // `onViewChange`; standalone embedders get internal state for free.
207
+ const [uncontrolledView, setUncontrolledView] = useState<string>("files");
208
+ const requestedView = view ?? uncontrolledView;
209
+ const handleViewChange = useCallback(
210
+ (next: string) => {
211
+ setUncontrolledView(next);
212
+ onViewChange?.(next);
213
+ },
214
+ [onViewChange],
215
+ );
216
+
217
+ // A stale id (e.g. a contextual extra view that disappeared) degrades to the
218
+ // explorer rather than an empty sidebar.
219
+ const isKnownView =
220
+ (BUILT_IN_VIEWS as readonly string[]).includes(requestedView) ||
221
+ (extraViews?.some((v) => v.id === requestedView) ?? false);
222
+ const activeView = isKnownView ? requestedView : "files";
223
+ const activeExtraView = extraViews?.find((v) => v.id === activeView);
224
+
225
+ return (
226
+ <div className={cn("flex h-full min-h-0", className)}>
227
+ <ActivityRail
228
+ view={activeView}
229
+ onViewChange={handleViewChange}
230
+ extraViews={extraViews}
231
+ />
232
+ <ResizableSplit
233
+ resizablePane="primary"
234
+ defaultSize={288}
235
+ minSize={200}
236
+ maxSize={560}
237
+ storageKey="stgm-workspace-sidebar-width"
238
+ ariaLabel="Resize sidebar"
239
+ // min-w-0: the split is a flex-row child of the surface; without it,
240
+ // the fixed-width sidebar sub-pane gives the whole surface a hard
241
+ // minimum width and content overflow escapes to the session panel.
242
+ className="min-h-0 min-w-0 flex-1"
243
+ primary={
244
+ activeExtraView ? (
245
+ <ExtraViewSidebar view={activeExtraView} />
246
+ ) : activeView === "search" ? (
247
+ <SearchSidebar
248
+ entries={entries}
249
+ lister={lister}
250
+ searcher={searcher}
251
+ selectedFile={selectedFile}
252
+ onOpenFile={onOpenFile}
253
+ />
254
+ ) : (
255
+ <ExplorerSidebar
256
+ entries={entries}
257
+ lister={lister}
258
+ selectedFile={selectedFile}
259
+ onOpenFile={onOpenFile}
260
+ onActivateFile={onPinEditor}
261
+ onRemoveEntry={onRemoveEntry}
262
+ onAddLocalFolder={onAddLocalFolder}
263
+ />
264
+ )
265
+ }
266
+ secondary={
267
+ <EditorArea
268
+ entries={entries}
269
+ reader={reader}
270
+ editors={editors}
271
+ selectedFile={selectedFile}
272
+ virtualDocuments={virtualDocuments}
273
+ change={change}
274
+ reveal={reveal}
275
+ onActivateEditor={onActivateEditor}
276
+ onPinEditor={onPinEditor}
277
+ onCloseEditor={onCloseEditor}
278
+ onCollapse={onCollapse}
279
+ />
280
+ }
281
+ />
282
+ </div>
283
+ );
284
+ }
285
+
286
+ // ---------------------------------------------------------------------------
287
+ // Activity rail — built-in views + host-injected extras (VS Code activity bar)
288
+ // ---------------------------------------------------------------------------
289
+
290
+ interface RailItem {
291
+ readonly id: string;
292
+ readonly label: string;
293
+ readonly icon: ReactNode;
294
+ readonly badge?: number;
295
+ }
296
+
297
+ function ActivityRail({
298
+ view,
299
+ onViewChange,
300
+ extraViews,
301
+ }: {
302
+ readonly view: string;
303
+ readonly onViewChange: (next: string) => void;
304
+ readonly extraViews: readonly SurfaceRailView[] | undefined;
305
+ }) {
306
+ const items: readonly RailItem[] = [
307
+ { id: "files", label: "Explorer", icon: <FilesIcon /> },
308
+ { id: "search", label: "Search", icon: <SearchIcon /> },
309
+ ...(extraViews ?? []),
310
+ ];
311
+
312
+ // One ref per rail button so selection can move DOM focus with it (below).
313
+ const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
314
+
315
+ // Roving radiogroup: selection follows focus. `keydown` fires on the focused
316
+ // button, so its `index` is the current position; we select the neighbour AND
317
+ // move focus to it. Moving focus is essential — without it focus stays pinned
318
+ // on the entered button (whose index never changes) and every later view
319
+ // (Config/Changes/…) is unreachable by keyboard.
320
+ const handleKeyDown = useCallback(
321
+ (e: KeyboardEvent<HTMLButtonElement>, index: number) => {
322
+ let nextIndex: number | null = null;
323
+ if (e.key === "ArrowDown" || e.key === "ArrowRight") {
324
+ nextIndex = Math.min(index + 1, items.length - 1);
325
+ } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
326
+ nextIndex = Math.max(index - 1, 0);
327
+ } else if (e.key === "Home") {
328
+ nextIndex = 0;
329
+ } else if (e.key === "End") {
330
+ nextIndex = items.length - 1;
331
+ }
332
+ if (nextIndex === null || nextIndex === index) return;
333
+ e.preventDefault();
334
+ onViewChange(items[nextIndex].id);
335
+ buttonRefs.current[nextIndex]?.focus();
336
+ },
337
+ [items, onViewChange],
338
+ );
339
+
340
+ return (
341
+ <div
342
+ role="radiogroup"
343
+ aria-label="Workspace view"
344
+ aria-orientation="vertical"
345
+ className="flex w-11 shrink-0 flex-col items-center gap-1 border-r border-border bg-muted-faint py-2"
346
+ >
347
+ {items.map((item, index) => {
348
+ const isSelected = view === item.id;
349
+ const showBadge = item.badge != null && item.badge > 0;
350
+ return (
351
+ <button
352
+ key={item.id}
353
+ ref={(el) => {
354
+ buttonRefs.current[index] = el;
355
+ }}
356
+ type="button"
357
+ role="radio"
358
+ aria-checked={isSelected}
359
+ aria-label={showBadge ? `${item.label} (${item.badge})` : item.label}
360
+ title={item.label}
361
+ tabIndex={isSelected ? 0 : -1}
362
+ onClick={() => onViewChange(item.id)}
363
+ onKeyDown={(e) => handleKeyDown(e, index)}
364
+ className={cn(
365
+ "relative flex h-9 w-9 items-center justify-center rounded-md transition-colors",
366
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
367
+ isSelected
368
+ ? "bg-accent text-foreground"
369
+ : "text-muted-foreground hover:bg-accent-hover hover:text-foreground",
370
+ )}
371
+ >
372
+ {item.icon}
373
+ {showBadge && (
374
+ <span
375
+ aria-hidden="true"
376
+ className="absolute -right-0.5 -top-0.5 inline-flex min-w-[0.875rem] items-center justify-center rounded-full bg-primary px-1 py-px text-[9px] font-medium leading-none text-primary-foreground"
377
+ >
378
+ {item.badge}
379
+ </span>
380
+ )}
381
+ </button>
382
+ );
383
+ })}
384
+ </div>
385
+ );
386
+ }
387
+
388
+ // ---------------------------------------------------------------------------
389
+ // Explorer sidebar — multi-root file tree
390
+ // ---------------------------------------------------------------------------
391
+
392
+ function ExplorerSidebar({
393
+ entries,
394
+ lister,
395
+ selectedFile,
396
+ onOpenFile,
397
+ onActivateFile,
398
+ onRemoveEntry,
399
+ onAddLocalFolder,
400
+ }: {
401
+ readonly entries: readonly WorkspaceEntry[];
402
+ readonly lister: WorkspaceFileLister | undefined;
403
+ readonly selectedFile: SelectedWorkspaceFile | null;
404
+ readonly onOpenFile: (entryId: string, path: string) => void;
405
+ readonly onActivateFile: (entryId: string, path: string) => void;
406
+ readonly onRemoveEntry?: (entryId: string) => void;
407
+ readonly onAddLocalFolder?: () => void;
408
+ }) {
409
+ return (
410
+ <div className="flex h-full min-h-0 flex-col border-r border-border">
411
+ <SidebarHeader title="Explorer" />
412
+ <div className="min-h-0 flex-1 overflow-y-auto py-1.5">
413
+ {!lister || entries.length === 0 ? (
414
+ <SidebarEmpty>No workspace attached.</SidebarEmpty>
415
+ ) : (
416
+ <ExplorerTree
417
+ entries={entries}
418
+ lister={lister}
419
+ selectedFile={selectedFile}
420
+ onOpenFile={onOpenFile}
421
+ onActivateFile={onActivateFile}
422
+ onRemoveEntry={onRemoveEntry}
423
+ />
424
+ )}
425
+ </div>
426
+ {onAddLocalFolder && (
427
+ <div className="shrink-0 border-t border-border-muted p-1.5">
428
+ <button
429
+ type="button"
430
+ onClick={onAddLocalFolder}
431
+ className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-xs text-foreground transition-colors hover:bg-accent-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
432
+ >
433
+ <FolderPlusIcon />
434
+ <span className="flex-1 text-left">Add Folder</span>
435
+ </button>
436
+ </div>
437
+ )}
438
+ </div>
439
+ );
440
+ }
441
+
442
+ // ---------------------------------------------------------------------------
443
+ // Search sidebar — reuses the workspace-wide search surface
444
+ // ---------------------------------------------------------------------------
445
+
446
+ type SearchMode = "name" | "text";
447
+
448
+ function SearchSidebar({
449
+ entries,
450
+ lister,
451
+ searcher,
452
+ selectedFile,
453
+ onOpenFile,
454
+ }: {
455
+ readonly entries: readonly WorkspaceEntry[];
456
+ readonly lister: WorkspaceFileLister | undefined;
457
+ readonly searcher: WorkspaceContentSearcher | undefined;
458
+ readonly selectedFile: SelectedWorkspaceFile | null;
459
+ readonly onOpenFile: (
460
+ entryId: string,
461
+ path: string,
462
+ options?: OpenFileOptions,
463
+ ) => void;
464
+ }) {
465
+ // Filename search is the default: cached, instant, and always available.
466
+ // Content (text) search is opt-in and only offered when a searcher exists.
467
+ const [mode, setMode] = useState<SearchMode>("name");
468
+
469
+ return (
470
+ <div className="flex h-full min-h-0 flex-col border-r border-border">
471
+ <SidebarHeader title="Search" />
472
+ {searcher && <SearchModeToggle value={mode} onChange={setMode} />}
473
+ {searcher && mode === "text" ? (
474
+ <WorkspaceContentSearch
475
+ entries={entries}
476
+ searcher={searcher}
477
+ onOpenFile={onOpenFile}
478
+ selectedFile={selectedFile}
479
+ className="min-h-0 flex-1"
480
+ />
481
+ ) : (
482
+ <WorkspaceFileSearch
483
+ entries={entries}
484
+ lister={lister}
485
+ onOpenFile={onOpenFile}
486
+ selectedFile={selectedFile}
487
+ className="min-h-0 flex-1"
488
+ />
489
+ )}
490
+ </div>
491
+ );
492
+ }
493
+
494
+ /**
495
+ * `Name | Text` search-mode selector, mirroring `FileViewer`'s `Diff | File`
496
+ * radiogroup (roving `tabindex`, arrow-key roving, `role="radio"`). Shown only
497
+ * when a content searcher is injected.
498
+ */
499
+ function SearchModeToggle({
500
+ value,
501
+ onChange,
502
+ }: {
503
+ readonly value: SearchMode;
504
+ readonly onChange: (next: SearchMode) => void;
505
+ }) {
506
+ const options: readonly { readonly value: SearchMode; readonly label: string }[] = [
507
+ { value: "name", label: "Name" },
508
+ { value: "text", label: "Text" },
509
+ ];
510
+
511
+ // Selection follows focus, matching ActivityRail / ViewerModeToggle.
512
+ const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
513
+ const selectMode = useCallback(
514
+ (next: SearchMode) => {
515
+ onChange(next);
516
+ buttonRefs.current[next === "name" ? 0 : 1]?.focus();
517
+ },
518
+ [onChange],
519
+ );
520
+ const handleKeyDown = useCallback(
521
+ (e: KeyboardEvent<HTMLButtonElement>) => {
522
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
523
+ e.preventDefault();
524
+ selectMode("text");
525
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
526
+ e.preventDefault();
527
+ selectMode("name");
528
+ }
529
+ },
530
+ [selectMode],
531
+ );
532
+
533
+ return (
534
+ <div className="shrink-0 border-b border-border px-2 py-1.5">
535
+ <div
536
+ role="radiogroup"
537
+ aria-label="Search mode"
538
+ className="inline-flex rounded-md bg-muted p-0.5"
539
+ >
540
+ {options.map((option, index) => {
541
+ const isSelected = value === option.value;
542
+ return (
543
+ <button
544
+ key={option.value}
545
+ ref={(el) => {
546
+ buttonRefs.current[index] = el;
547
+ }}
548
+ type="button"
549
+ role="radio"
550
+ aria-checked={isSelected}
551
+ tabIndex={isSelected ? 0 : -1}
552
+ onClick={() => onChange(option.value)}
553
+ onKeyDown={handleKeyDown}
554
+ className={cn(
555
+ "rounded px-2.5 py-0.5 text-[0.7rem] font-medium transition-colors",
556
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
557
+ isSelected
558
+ ? "bg-background text-foreground shadow-sm"
559
+ : "text-muted-foreground hover:text-foreground",
560
+ )}
561
+ >
562
+ {option.label}
563
+ </button>
564
+ );
565
+ })}
566
+ </div>
567
+ </div>
568
+ );
569
+ }
570
+
571
+ // ---------------------------------------------------------------------------
572
+ // Extra-view sidebar — host-injected facet content (Config, Changes, …)
573
+ // ---------------------------------------------------------------------------
574
+
575
+ function ExtraViewSidebar({ view }: { readonly view: SurfaceRailView }) {
576
+ return (
577
+ <div className="flex h-full min-h-0 flex-col border-r border-border">
578
+ <SidebarHeader title={view.label} />
579
+ {/* Same scroll + padding envelope the inspector gave these components,
580
+ so facet content drops in unchanged. */}
581
+ <div className="min-h-0 flex-1 overflow-y-auto px-3 py-3">
582
+ {view.content}
583
+ </div>
584
+ </div>
585
+ );
586
+ }
587
+
588
+ // ---------------------------------------------------------------------------
589
+ // Editor area — collapse control + tab strip + read-only FileViewer
590
+ // ---------------------------------------------------------------------------
591
+
592
+ function EditorArea({
593
+ entries,
594
+ reader,
595
+ editors,
596
+ selectedFile,
597
+ virtualDocuments,
598
+ change,
599
+ reveal,
600
+ onActivateEditor,
601
+ onPinEditor,
602
+ onCloseEditor,
603
+ onCollapse,
604
+ }: {
605
+ readonly entries: readonly WorkspaceEntry[];
606
+ readonly reader: WorkspaceFileReader | undefined;
607
+ readonly editors: readonly OpenEditor[];
608
+ readonly selectedFile: SelectedWorkspaceFile | null;
609
+ readonly virtualDocuments?: readonly SurfaceVirtualDocument[];
610
+ readonly change?: FileChange;
611
+ readonly reveal?: RevealTarget;
612
+ readonly onActivateEditor: (entryId: string, path: string) => void;
613
+ readonly onPinEditor: (entryId: string, path: string) => void;
614
+ readonly onCloseEditor: (entryId: string, path: string) => void;
615
+ readonly onCollapse: () => void;
616
+ }) {
617
+ const viewerRef = useRef<FileViewerHandle>(null);
618
+ // Instance-scoped ids tie each tab to the single editor body (tabpanel).
619
+ const idBase = useId();
620
+ const panelId = `${idBase}editor-panel`;
621
+ const tabIdPrefix = `${idBase}editor-tab-`;
622
+ const activeKey = selectedFile
623
+ ? editorKey(selectedFile.entryId, selectedFile.path)
624
+ : null;
625
+ // The active tab is a virtual document when its identity matches an injected
626
+ // one — the body renders the document's content instead of the file viewer.
627
+ const activeVirtualDocument =
628
+ activeKey !== null
629
+ ? virtualDocuments?.find(
630
+ (d) => editorKey(d.entryId, d.path) === activeKey,
631
+ )
632
+ : undefined;
633
+ const activeEntryName = selectedFile
634
+ ? entries.find((e) => e.id === selectedFile.entryId)?.name
635
+ : undefined;
636
+
637
+ return (
638
+ <div className="flex h-full min-h-0 flex-col">
639
+ {/* Collapse control + tab strip. The far right is kept clear (pr-24):
640
+ the session viewer floats its top-right controls (host
641
+ `headerActions` and the panel chip) over this region. */}
642
+ <div className="flex shrink-0 items-stretch border-b border-border pr-24">
643
+ <button
644
+ type="button"
645
+ onClick={onCollapse}
646
+ aria-label="Back to chat"
647
+ title="Back to chat"
648
+ className="flex shrink-0 items-center border-r border-border px-2 text-muted-foreground transition-colors hover:bg-accent-hover hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
649
+ >
650
+ <ChevronLeftIcon />
651
+ </button>
652
+ {editors.length > 0 && (
653
+ <EditorTabs
654
+ editors={editors}
655
+ activeKey={activeKey}
656
+ onActivate={onActivateEditor}
657
+ onPin={onPinEditor}
658
+ onClose={onCloseEditor}
659
+ panelId={panelId}
660
+ tabIdPrefix={tabIdPrefix}
661
+ className="min-w-0 flex-1 border-b-0"
662
+ />
663
+ )}
664
+ </div>
665
+ {/* The single editor body — the one tabpanel the tab strip controls (the
666
+ content swaps; the panel is stable). role/labelledby apply only with an
667
+ active tab (the empty state has no tab to label). Carries the same
668
+ flex/min-w-0 chain as the branches so the DD-20 reflow is preserved:
669
+ the wrapper is just a labelled passthrough, not a new layout context. */}
670
+ <div
671
+ className="flex min-h-0 min-w-0 flex-1 flex-col"
672
+ {...(activeKey
673
+ ? {
674
+ role: "tabpanel",
675
+ id: panelId,
676
+ "aria-labelledby": editorTabDomId(tabIdPrefix, activeKey),
677
+ }
678
+ : {})}
679
+ >
680
+ {activeVirtualDocument ? (
681
+ // A virtual document owns its body wholesale: no breadcrumbs (there is
682
+ // no filesystem location) and no FileViewer (there is no reader-backed
683
+ // file). Keyed like files so switching documents resets cleanly.
684
+ // Vertical-only scrolling: a lone overflow-y-auto would compute
685
+ // overflow-x to auto too, giving the pane a horizontal scrollbar the
686
+ // moment any child refuses to shrink — documents must reflow instead.
687
+ <div
688
+ key={activeKey ?? undefined}
689
+ className="min-h-0 min-w-0 flex-1 overflow-y-auto overflow-x-hidden"
690
+ >
691
+ {activeVirtualDocument.content}
692
+ </div>
693
+ ) : selectedFile ? (
694
+ <>
695
+ <Breadcrumbs
696
+ entryName={activeEntryName}
697
+ path={selectedFile.path}
698
+ onRefresh={reader ? () => viewerRef.current?.refresh() : undefined}
699
+ />
700
+ {/* Per-file remount (key) resets scroll and view-mode state cleanly.
701
+ The tab strip + breadcrumbs own the file identity, close, and
702
+ refresh, so the viewer is chrome-less; Escape still collapses. */}
703
+ <FileViewer
704
+ ref={viewerRef}
705
+ key={activeKey ?? undefined}
706
+ selectedFile={selectedFile}
707
+ entries={entries}
708
+ reader={reader}
709
+ change={change}
710
+ reveal={reveal}
711
+ onClose={onCollapse}
712
+ showHeader={false}
713
+ className="min-h-0 flex-1"
714
+ />
715
+ </>
716
+ ) : (
717
+ <div className="flex flex-1 items-center justify-center p-8 text-center text-xs text-muted-foreground">
718
+ Select a file to view its contents.
719
+ </div>
720
+ )}
721
+ </div>
722
+ </div>
723
+ );
724
+ }
725
+
726
+ /**
727
+ * Location breadcrumb for the active file: entry › folder › … › file, with an
728
+ * optional refresh. Display-only orientation (clickable navigation is a future
729
+ * enhancement); the refresh re-fetches the live file content.
730
+ */
731
+ function Breadcrumbs({
732
+ entryName,
733
+ path,
734
+ onRefresh,
735
+ }: {
736
+ readonly entryName: string | undefined;
737
+ readonly path: string;
738
+ readonly onRefresh?: () => void;
739
+ }) {
740
+ const segments = path.split("/").filter(Boolean);
741
+ const crumbs = entryName ? [entryName, ...segments] : segments;
742
+
743
+ return (
744
+ <div className="flex shrink-0 items-center gap-1 border-b border-border-muted px-3 py-1 text-[0.65rem] text-muted-foreground">
745
+ <nav aria-label="File location" className="flex min-w-0 flex-1 items-center gap-1">
746
+ {crumbs.map((crumb, i) => {
747
+ const isLast = i === crumbs.length - 1;
748
+ return (
749
+ <span key={i} className="flex min-w-0 items-center gap-1">
750
+ {i > 0 && <span className="text-muted-foreground-subtle" aria-hidden="true">›</span>}
751
+ <span className={cn("truncate", isLast && "text-foreground")}>
752
+ {crumb}
753
+ </span>
754
+ </span>
755
+ );
756
+ })}
757
+ </nav>
758
+ {onRefresh && (
759
+ <button
760
+ type="button"
761
+ onClick={onRefresh}
762
+ aria-label="Reload file"
763
+ title="Reload file"
764
+ className="shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
765
+ >
766
+ <RefreshIcon />
767
+ </button>
768
+ )}
769
+ </div>
770
+ );
771
+ }
772
+
773
+ // ---------------------------------------------------------------------------
774
+ // Shared sidebar chrome
775
+ // ---------------------------------------------------------------------------
776
+
777
+ function SidebarHeader({ title }: { readonly title: string }) {
778
+ return (
779
+ <div className="shrink-0 border-b border-border px-3 py-2 text-[0.65rem] font-semibold uppercase tracking-wide text-muted-foreground">
780
+ {title}
781
+ </div>
782
+ );
783
+ }
784
+
785
+ function SidebarEmpty({ children }: { readonly children: React.ReactNode }) {
786
+ return (
787
+ <div className="px-2 py-6 text-center text-xs text-muted-foreground">
788
+ {children}
789
+ </div>
790
+ );
791
+ }
792
+
793
+ // ---------------------------------------------------------------------------
794
+ // Inline SVG icons (SDK independence — no lucide dependency)
795
+ // ---------------------------------------------------------------------------
796
+
797
+ function FilesIcon() {
798
+ return (
799
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
800
+ <path d="M13 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
801
+ <path d="M13 3v5h5" />
802
+ </svg>
803
+ );
804
+ }
805
+
806
+ function SearchIcon() {
807
+ return (
808
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
809
+ <circle cx="11" cy="11" r="7" />
810
+ <path d="M21 21l-4.3-4.3" />
811
+ </svg>
812
+ );
813
+ }
814
+
815
+ function FolderPlusIcon() {
816
+ return (
817
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="shrink-0 text-muted-foreground" aria-hidden="true">
818
+ <path d="M1.5 3.5V11a1 1 0 001 1h9a1 1 0 001-1V5.5a1 1 0 00-1-1H7L5.5 3H2.5a1 1 0 00-1 .5z" />
819
+ <path d="M7 7v3M5.5 8.5h3" />
820
+ </svg>
821
+ );
822
+ }
823
+
824
+ function ChevronLeftIcon() {
825
+ return (
826
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
827
+ <path d="M10 3L5 8L10 13" />
828
+ </svg>
829
+ );
830
+ }
831
+
832
+ function RefreshIcon() {
833
+ return (
834
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
835
+ <path d="M1.5 6a4.5 4.5 0 017.794-3.073M10.5 6a4.5 4.5 0 01-7.794 3.073" />
836
+ <path d="M9.5 1v2.5H7M2.5 11V8.5H5" />
837
+ </svg>
838
+ );
839
+ }