@stigmer/react 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (426) hide show
  1. package/composer/SessionComposer.d.ts +24 -1
  2. package/composer/SessionComposer.d.ts.map +1 -1
  3. package/composer/SessionComposer.js +19 -8
  4. package/composer/SessionComposer.js.map +1 -1
  5. package/execution/ArtifactContentRenderer.d.ts +11 -1
  6. package/execution/ArtifactContentRenderer.d.ts.map +1 -1
  7. package/execution/ArtifactContentRenderer.js +39 -15
  8. package/execution/ArtifactContentRenderer.js.map +1 -1
  9. package/execution/FilePathContext.d.ts +8 -1
  10. package/execution/FilePathContext.d.ts.map +1 -1
  11. package/execution/FilePathContext.js.map +1 -1
  12. package/execution/FilePathLink.d.ts.map +1 -1
  13. package/execution/FilePathLink.js +21 -6
  14. package/execution/FilePathLink.js.map +1 -1
  15. package/execution/MessageEntry.d.ts +29 -0
  16. package/execution/MessageEntry.d.ts.map +1 -1
  17. package/execution/MessageEntry.js +29 -5
  18. package/execution/MessageEntry.js.map +1 -1
  19. package/execution/MessageThread.d.ts +76 -13
  20. package/execution/MessageThread.d.ts.map +1 -1
  21. package/execution/MessageThread.js +178 -24
  22. package/execution/MessageThread.js.map +1 -1
  23. package/execution/PlanArtifactCard.d.ts +27 -18
  24. package/execution/PlanArtifactCard.d.ts.map +1 -1
  25. package/execution/PlanArtifactCard.js +13 -23
  26. package/execution/PlanArtifactCard.js.map +1 -1
  27. package/execution/PlanDocumentMessage.d.ts +32 -0
  28. package/execution/PlanDocumentMessage.d.ts.map +1 -0
  29. package/execution/PlanDocumentMessage.js +37 -0
  30. package/execution/PlanDocumentMessage.js.map +1 -0
  31. package/execution/PlanStreamingCard.d.ts +45 -0
  32. package/execution/PlanStreamingCard.d.ts.map +1 -0
  33. package/execution/PlanStreamingCard.js +35 -0
  34. package/execution/PlanStreamingCard.js.map +1 -0
  35. package/execution/index.d.ts +5 -1
  36. package/execution/index.d.ts.map +1 -1
  37. package/execution/index.js +2 -0
  38. package/execution/index.js.map +1 -1
  39. package/execution/useCreateAgentExecution.d.ts +10 -0
  40. package/execution/useCreateAgentExecution.d.ts.map +1 -1
  41. package/execution/useCreateAgentExecution.js +5 -1
  42. package/execution/useCreateAgentExecution.js.map +1 -1
  43. package/github/decodeGitHubContent.d.ts +29 -0
  44. package/github/decodeGitHubContent.d.ts.map +1 -0
  45. package/github/decodeGitHubContent.js +72 -0
  46. package/github/decodeGitHubContent.js.map +1 -0
  47. package/github/index.d.ts +1 -0
  48. package/github/index.d.ts.map +1 -1
  49. package/github/index.js +1 -0
  50. package/github/index.js.map +1 -1
  51. package/github/useGitHubFileReader.d.ts +26 -0
  52. package/github/useGitHubFileReader.d.ts.map +1 -0
  53. package/github/useGitHubFileReader.js +106 -0
  54. package/github/useGitHubFileReader.js.map +1 -0
  55. package/github/useGitHubTreeLister.d.ts.map +1 -1
  56. package/github/useGitHubTreeLister.js +6 -2
  57. package/github/useGitHubTreeLister.js.map +1 -1
  58. package/index.d.ts +9 -9
  59. package/index.d.ts.map +1 -1
  60. package/index.js +5 -5
  61. package/index.js.map +1 -1
  62. package/internal/ResizableSplit.d.ts +72 -22
  63. package/internal/ResizableSplit.d.ts.map +1 -1
  64. package/internal/ResizableSplit.js +96 -38
  65. package/internal/ResizableSplit.js.map +1 -1
  66. package/internal/VirtualizedThread.d.ts +5 -3
  67. package/internal/VirtualizedThread.d.ts.map +1 -1
  68. package/internal/VirtualizedThread.js +6 -4
  69. package/internal/VirtualizedThread.js.map +1 -1
  70. package/internal/file-icons/index.d.ts +17 -0
  71. package/internal/file-icons/index.d.ts.map +1 -0
  72. package/internal/file-icons/index.js +60 -0
  73. package/internal/file-icons/index.js.map +1 -0
  74. package/internal/file-tree/FileTreeNode.d.ts +18 -1
  75. package/internal/file-tree/FileTreeNode.d.ts.map +1 -1
  76. package/internal/file-tree/FileTreeNode.js +11 -2
  77. package/internal/file-tree/FileTreeNode.js.map +1 -1
  78. package/internal/markdown-components.d.ts +38 -1
  79. package/internal/markdown-components.d.ts.map +1 -1
  80. package/internal/markdown-components.js +86 -11
  81. package/internal/markdown-components.js.map +1 -1
  82. package/{workflow → internal}/motion-preference.d.ts +7 -4
  83. package/internal/motion-preference.d.ts.map +1 -0
  84. package/{workflow → internal}/motion-preference.js +7 -4
  85. package/internal/motion-preference.js.map +1 -0
  86. package/internal/store/index.d.ts +33 -0
  87. package/internal/store/index.d.ts.map +1 -1
  88. package/internal/store/index.js +59 -0
  89. package/internal/store/index.js.map +1 -1
  90. package/internal/store/workspace-editors-store.d.ts +145 -0
  91. package/internal/store/workspace-editors-store.d.ts.map +1 -0
  92. package/internal/store/workspace-editors-store.js +208 -0
  93. package/internal/store/workspace-editors-store.js.map +1 -0
  94. package/internal/store/workspace-file-selection-store.d.ts +38 -0
  95. package/internal/store/workspace-file-selection-store.d.ts.map +1 -0
  96. package/internal/store/workspace-file-selection-store.js +62 -0
  97. package/internal/store/workspace-file-selection-store.js.map +1 -0
  98. package/internal/useRevealLine.d.ts +49 -0
  99. package/internal/useRevealLine.d.ts.map +1 -0
  100. package/internal/useRevealLine.js +43 -0
  101. package/internal/useRevealLine.js.map +1 -0
  102. package/library/detect-plan-artifact.d.ts +21 -0
  103. package/library/detect-plan-artifact.d.ts.map +1 -1
  104. package/library/detect-plan-artifact.js +20 -0
  105. package/library/detect-plan-artifact.js.map +1 -1
  106. package/library/detect-streaming-plan.d.ts +45 -0
  107. package/library/detect-streaming-plan.d.ts.map +1 -0
  108. package/library/detect-streaming-plan.js +83 -0
  109. package/library/detect-streaming-plan.js.map +1 -0
  110. package/library/index.d.ts +4 -1
  111. package/library/index.d.ts.map +1 -1
  112. package/library/index.js +2 -1
  113. package/library/index.js.map +1 -1
  114. package/package.json +4 -4
  115. package/session/NewSessionViewer.d.ts +19 -4
  116. package/session/NewSessionViewer.d.ts.map +1 -1
  117. package/session/NewSessionViewer.js +52 -25
  118. package/session/NewSessionViewer.js.map +1 -1
  119. package/session/PlanEditor.d.ts +55 -0
  120. package/session/PlanEditor.d.ts.map +1 -0
  121. package/session/PlanEditor.js +88 -0
  122. package/session/PlanEditor.js.map +1 -0
  123. package/session/PlanStreamingDocument.d.ts +30 -0
  124. package/session/PlanStreamingDocument.d.ts.map +1 -0
  125. package/session/PlanStreamingDocument.js +37 -0
  126. package/session/PlanStreamingDocument.js.map +1 -0
  127. package/session/SessionPanelChip.d.ts +26 -0
  128. package/session/SessionPanelChip.d.ts.map +1 -0
  129. package/session/SessionPanelChip.js +40 -0
  130. package/session/SessionPanelChip.js.map +1 -0
  131. package/session/SessionViewer.d.ts +34 -7
  132. package/session/SessionViewer.d.ts.map +1 -1
  133. package/session/SessionViewer.js +315 -33
  134. package/session/SessionViewer.js.map +1 -1
  135. package/session/{inspector → facets}/ArtifactsTab.d.ts +10 -2
  136. package/session/facets/ArtifactsTab.d.ts.map +1 -0
  137. package/session/{inspector → facets}/ArtifactsTab.js +8 -3
  138. package/session/facets/ArtifactsTab.js.map +1 -0
  139. package/session/{inspector → facets}/ChangesTab.d.ts +3 -3
  140. package/session/facets/ChangesTab.d.ts.map +1 -0
  141. package/session/{inspector → facets}/ChangesTab.js +3 -3
  142. package/session/facets/ChangesTab.js.map +1 -0
  143. package/session/facets/InspectTab.d.ts.map +1 -0
  144. package/session/facets/InspectTab.js.map +1 -0
  145. package/session/{inspector → facets}/SetupTab.d.ts +11 -2
  146. package/session/facets/SetupTab.d.ts.map +1 -0
  147. package/session/{inspector → facets}/SetupTab.js +4 -3
  148. package/session/facets/SetupTab.js.map +1 -0
  149. package/session/{inspector → facets}/UsageTab.d.ts +1 -1
  150. package/session/facets/UsageTab.d.ts.map +1 -0
  151. package/session/{inspector → facets}/UsageTab.js +1 -1
  152. package/session/facets/UsageTab.js.map +1 -0
  153. package/session/{inspector → facets}/index.d.ts +0 -6
  154. package/session/facets/index.d.ts.map +1 -0
  155. package/session/{inspector → facets}/index.js +0 -3
  156. package/session/facets/index.js.map +1 -0
  157. package/session/index.d.ts +11 -2
  158. package/session/index.d.ts.map +1 -1
  159. package/session/index.js +15 -2
  160. package/session/index.js.map +1 -1
  161. package/session/plan-document.d.ts +9 -0
  162. package/session/plan-document.d.ts.map +1 -0
  163. package/session/plan-document.js +17 -0
  164. package/session/plan-document.js.map +1 -0
  165. package/session/useOpenFileChange.d.ts +20 -0
  166. package/session/useOpenFileChange.d.ts.map +1 -0
  167. package/session/useOpenFileChange.js +31 -0
  168. package/session/useOpenFileChange.js.map +1 -0
  169. package/session/usePlanDraft.d.ts +52 -0
  170. package/session/usePlanDraft.d.ts.map +1 -0
  171. package/session/usePlanDraft.js +63 -0
  172. package/session/usePlanDraft.js.map +1 -0
  173. package/session/useSessionConversation.d.ts +6 -0
  174. package/session/useSessionConversation.d.ts.map +1 -1
  175. package/session/useSessionConversation.js +1 -0
  176. package/session/useSessionConversation.js.map +1 -1
  177. package/session/useSessionPageFlow.d.ts.map +1 -1
  178. package/session/useSessionPageFlow.js +1 -0
  179. package/session/useSessionPageFlow.js.map +1 -1
  180. package/session/useSessionPanel.d.ts +120 -0
  181. package/session/useSessionPanel.d.ts.map +1 -0
  182. package/session/useSessionPanel.js +130 -0
  183. package/session/useSessionPanel.js.map +1 -0
  184. package/session/useSessionRailViews.d.ts +45 -0
  185. package/session/useSessionRailViews.d.ts.map +1 -0
  186. package/session/useSessionRailViews.js +105 -0
  187. package/session/useSessionRailViews.js.map +1 -0
  188. package/src/composer/SessionComposer.tsx +52 -10
  189. package/src/composer/__tests__/SessionComposer-contract.test.tsx +58 -4
  190. package/src/execution/ArtifactContentRenderer.tsx +101 -24
  191. package/src/execution/FilePathContext.tsx +8 -1
  192. package/src/execution/FilePathLink.tsx +22 -6
  193. package/src/execution/MessageEntry.tsx +111 -1
  194. package/src/execution/MessageThread.tsx +291 -36
  195. package/src/execution/PlanArtifactCard.tsx +63 -31
  196. package/src/execution/PlanDocumentMessage.tsx +96 -0
  197. package/src/execution/PlanStreamingCard.tsx +137 -0
  198. package/src/execution/__tests__/ArtifactContentRenderer.test.tsx +80 -1
  199. package/src/execution/__tests__/FilePathLink.test.tsx +87 -0
  200. package/src/execution/__tests__/MessageThread.test.tsx +101 -0
  201. package/src/execution/__tests__/PlanArtifactCard.test.tsx +66 -12
  202. package/src/execution/__tests__/PlanDocumentMessage.test.tsx +55 -0
  203. package/src/execution/__tests__/PlanStreamingCard.test.tsx +49 -0
  204. package/src/execution/__tests__/buildThreadItems-plan-completion.test.ts +504 -22
  205. package/src/execution/__tests__/message-entry.test.tsx +48 -0
  206. package/src/execution/__tests__/useCreateAgentExecution.test.tsx +85 -0
  207. package/src/execution/index.ts +7 -1
  208. package/src/execution/useCreateAgentExecution.ts +16 -1
  209. package/src/github/__tests__/decodeGitHubContent.test.ts +102 -0
  210. package/src/github/__tests__/useGitHubFileReader.test.ts +183 -0
  211. package/src/github/decodeGitHubContent.ts +83 -0
  212. package/src/github/index.ts +1 -0
  213. package/src/github/useGitHubFileReader.ts +152 -0
  214. package/src/github/useGitHubTreeLister.ts +6 -2
  215. package/src/index.ts +63 -9
  216. package/src/internal/ResizableSplit.tsx +184 -51
  217. package/src/internal/VirtualizedThread.tsx +12 -4
  218. package/src/internal/__tests__/ResizableSplit.test.tsx +125 -3
  219. package/src/internal/__tests__/markdown-components.test.tsx +68 -0
  220. package/src/internal/__tests__/useRevealLine.test.tsx +97 -0
  221. package/src/internal/file-icons/__tests__/file-icons.test.tsx +35 -0
  222. package/src/internal/file-icons/index.tsx +137 -0
  223. package/src/internal/file-tree/FileTreeNode.tsx +43 -3
  224. package/src/internal/file-tree/__tests__/FileTreeNode-explorer.test.tsx +86 -0
  225. package/src/internal/markdown-components.tsx +116 -11
  226. package/src/{workflow → internal}/motion-preference.ts +7 -4
  227. package/src/internal/store/__tests__/workspace-editors-store.test.ts +216 -0
  228. package/src/internal/store/__tests__/workspace-file-selection-store.test.ts +118 -0
  229. package/src/internal/store/index.ts +94 -0
  230. package/src/internal/store/workspace-editors-store.ts +315 -0
  231. package/src/internal/store/workspace-file-selection-store.ts +88 -0
  232. package/src/internal/useRevealLine.ts +81 -0
  233. package/src/library/__tests__/detect-plan-artifact.test.ts +48 -0
  234. package/src/library/__tests__/detect-streaming-plan.test.ts +234 -0
  235. package/src/library/detect-plan-artifact.ts +35 -0
  236. package/src/library/detect-streaming-plan.ts +108 -0
  237. package/src/library/index.ts +5 -0
  238. package/src/session/NewSessionViewer.tsx +123 -55
  239. package/src/session/PlanEditor.tsx +361 -0
  240. package/src/session/PlanStreamingDocument.tsx +97 -0
  241. package/src/session/SessionPanelChip.tsx +103 -0
  242. package/src/session/SessionViewer.tsx +617 -90
  243. package/src/session/__tests__/NewSessionViewer.test.tsx +148 -0
  244. package/src/session/__tests__/PlanEditor.test.tsx +217 -0
  245. package/src/session/__tests__/PlanStreamingDocument.test.tsx +50 -0
  246. package/src/session/__tests__/SessionPanelChip.test.tsx +42 -0
  247. package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +9 -4
  248. package/src/session/__tests__/SessionViewer-transcriptFileOpen.test.tsx +205 -0
  249. package/src/session/__tests__/audienceWiring.test.tsx +55 -19
  250. package/src/session/__tests__/useOpenFileChange.test.tsx +82 -0
  251. package/src/session/__tests__/usePlanDraft.test.ts +102 -0
  252. package/src/session/__tests__/useSessionPanel.test.ts +298 -0
  253. package/src/session/__tests__/useSessionRailViews.test.tsx +124 -0
  254. package/src/session/{inspector → facets}/ArtifactsTab.tsx +27 -5
  255. package/src/session/{inspector → facets}/ChangesTab.tsx +3 -3
  256. package/src/session/{inspector → facets}/SetupTab.tsx +13 -1
  257. package/src/session/{inspector → facets}/UsageTab.tsx +1 -1
  258. package/src/session/{inspector → facets}/__tests__/ArtifactsTab.test.tsx +35 -1
  259. package/src/session/facets/__tests__/SetupTab.test.tsx +44 -0
  260. package/src/session/{inspector → facets}/index.ts +0 -16
  261. package/src/session/index.ts +30 -13
  262. package/src/session/plan-document.ts +19 -0
  263. package/src/session/useOpenFileChange.ts +53 -0
  264. package/src/session/usePlanDraft.ts +109 -0
  265. package/src/session/useSessionConversation.ts +7 -0
  266. package/src/session/useSessionPageFlow.ts +1 -0
  267. package/src/session/useSessionPanel.ts +288 -0
  268. package/src/session/useSessionRailViews.tsx +200 -0
  269. package/src/workflow/WorkflowExecutionGraph.tsx +1 -1
  270. package/src/workflow/index.ts +1 -1
  271. package/src/workflow/useFollowExecution.ts +1 -1
  272. package/src/workspace/EditorTabs.tsx +145 -0
  273. package/src/workspace/ExplorerTree.tsx +247 -0
  274. package/src/workspace/FileViewer.tsx +516 -0
  275. package/src/workspace/WorkspaceContentSearch.tsx +478 -0
  276. package/src/workspace/WorkspaceContentSearcher.ts +70 -0
  277. package/src/workspace/WorkspaceFileLister.ts +11 -0
  278. package/src/workspace/WorkspaceFileReader.ts +57 -0
  279. package/src/workspace/WorkspaceFileSearch.tsx +429 -0
  280. package/src/workspace/WorkspaceSurface.tsx +781 -0
  281. package/src/workspace/__tests__/EditorTabs.test.tsx +100 -0
  282. package/src/workspace/__tests__/ExplorerTree.test.tsx +152 -0
  283. package/src/workspace/__tests__/FileViewer.test.tsx +421 -0
  284. package/src/workspace/__tests__/WorkspaceContentSearch.test.tsx +196 -0
  285. package/src/workspace/__tests__/WorkspaceFileSearch.test.tsx +206 -0
  286. package/src/workspace/__tests__/WorkspaceSurface.test.tsx +348 -0
  287. package/src/workspace/__tests__/findChangeForSelection.test.ts +132 -0
  288. package/src/workspace/__tests__/matchContentPreview.test.ts +55 -0
  289. package/src/workspace/__tests__/matchWorkspaceFiles.test.ts +104 -0
  290. package/src/workspace/__tests__/resolveWorkspaceFileSelection.test.ts +135 -0
  291. package/src/workspace/__tests__/useWorkspaceContentSearch.test.tsx +196 -0
  292. package/src/workspace/__tests__/useWorkspaceFileContent.test.tsx +190 -0
  293. package/src/workspace/__tests__/useWorkspaceFileSearch.test.tsx +162 -0
  294. package/src/workspace/findChangeForSelection.ts +61 -0
  295. package/src/workspace/index.ts +56 -2
  296. package/src/workspace/matchContentPreview.ts +51 -0
  297. package/src/workspace/matchWorkspaceFiles.ts +86 -0
  298. package/src/workspace/resolveWorkspaceFileSelection.ts +119 -0
  299. package/src/workspace/useWorkspaceContentSearch.ts +228 -0
  300. package/src/workspace/useWorkspaceFileContent.ts +129 -0
  301. package/src/workspace/useWorkspaceFileSearch.ts +184 -0
  302. package/src/workspace/useWorkspaceFiles.ts +27 -27
  303. package/src/workspace/workspaceListingCache.ts +93 -0
  304. package/styles.css +1 -1
  305. package/workflow/WorkflowExecutionGraph.js +1 -1
  306. package/workflow/WorkflowExecutionGraph.js.map +1 -1
  307. package/workflow/index.d.ts +1 -1
  308. package/workflow/index.d.ts.map +1 -1
  309. package/workflow/index.js +1 -1
  310. package/workflow/index.js.map +1 -1
  311. package/workflow/useFollowExecution.js +1 -1
  312. package/workflow/useFollowExecution.js.map +1 -1
  313. package/workspace/EditorTabs.d.ts +29 -0
  314. package/workspace/EditorTabs.d.ts.map +1 -0
  315. package/workspace/EditorTabs.js +66 -0
  316. package/workspace/EditorTabs.js.map +1 -0
  317. package/workspace/ExplorerTree.d.ts +32 -0
  318. package/workspace/ExplorerTree.d.ts.map +1 -0
  319. package/workspace/ExplorerTree.js +45 -0
  320. package/workspace/ExplorerTree.js.map +1 -0
  321. package/workspace/FileViewer.d.ts +93 -0
  322. package/workspace/FileViewer.d.ts.map +1 -0
  323. package/workspace/FileViewer.js +182 -0
  324. package/workspace/FileViewer.js.map +1 -0
  325. package/workspace/WorkspaceContentSearch.d.ts +41 -0
  326. package/workspace/WorkspaceContentSearch.d.ts.map +1 -0
  327. package/workspace/WorkspaceContentSearch.js +195 -0
  328. package/workspace/WorkspaceContentSearch.js.map +1 -0
  329. package/workspace/WorkspaceContentSearcher.d.ts +59 -0
  330. package/workspace/WorkspaceContentSearcher.d.ts.map +1 -0
  331. package/workspace/WorkspaceContentSearcher.js +7 -0
  332. package/workspace/WorkspaceContentSearcher.js.map +1 -0
  333. package/workspace/WorkspaceFileLister.d.ts +11 -0
  334. package/workspace/WorkspaceFileLister.d.ts.map +1 -1
  335. package/workspace/WorkspaceFileReader.d.ts +48 -0
  336. package/workspace/WorkspaceFileReader.d.ts.map +1 -0
  337. package/workspace/WorkspaceFileReader.js +11 -0
  338. package/workspace/WorkspaceFileReader.js.map +1 -0
  339. package/workspace/WorkspaceFileSearch.d.ts +32 -0
  340. package/workspace/WorkspaceFileSearch.d.ts.map +1 -0
  341. package/workspace/WorkspaceFileSearch.js +155 -0
  342. package/workspace/WorkspaceFileSearch.js.map +1 -0
  343. package/workspace/WorkspaceSurface.d.ts +151 -0
  344. package/workspace/WorkspaceSurface.d.ts.map +1 -0
  345. package/workspace/WorkspaceSurface.js +184 -0
  346. package/workspace/WorkspaceSurface.js.map +1 -0
  347. package/workspace/findChangeForSelection.d.ts +19 -0
  348. package/workspace/findChangeForSelection.d.ts.map +1 -0
  349. package/workspace/findChangeForSelection.js +46 -0
  350. package/workspace/findChangeForSelection.js.map +1 -0
  351. package/workspace/index.d.ts +24 -2
  352. package/workspace/index.d.ts.map +1 -1
  353. package/workspace/index.js +13 -1
  354. package/workspace/index.js.map +1 -1
  355. package/workspace/matchContentPreview.d.ts +21 -0
  356. package/workspace/matchContentPreview.d.ts.map +1 -0
  357. package/workspace/matchContentPreview.js +36 -0
  358. package/workspace/matchContentPreview.js.map +1 -0
  359. package/workspace/matchWorkspaceFiles.d.ts +30 -0
  360. package/workspace/matchWorkspaceFiles.d.ts.map +1 -0
  361. package/workspace/matchWorkspaceFiles.js +58 -0
  362. package/workspace/matchWorkspaceFiles.js.map +1 -0
  363. package/workspace/resolveWorkspaceFileSelection.d.ts +19 -0
  364. package/workspace/resolveWorkspaceFileSelection.d.ts.map +1 -0
  365. package/workspace/resolveWorkspaceFileSelection.js +115 -0
  366. package/workspace/resolveWorkspaceFileSelection.js.map +1 -0
  367. package/workspace/useWorkspaceContentSearch.d.ts +69 -0
  368. package/workspace/useWorkspaceContentSearch.d.ts.map +1 -0
  369. package/workspace/useWorkspaceContentSearch.js +154 -0
  370. package/workspace/useWorkspaceContentSearch.js.map +1 -0
  371. package/workspace/useWorkspaceFileContent.d.ts +73 -0
  372. package/workspace/useWorkspaceFileContent.d.ts.map +1 -0
  373. package/workspace/useWorkspaceFileContent.js +57 -0
  374. package/workspace/useWorkspaceFileContent.js.map +1 -0
  375. package/workspace/useWorkspaceFileSearch.d.ts +61 -0
  376. package/workspace/useWorkspaceFileSearch.d.ts.map +1 -0
  377. package/workspace/useWorkspaceFileSearch.js +119 -0
  378. package/workspace/useWorkspaceFileSearch.js.map +1 -0
  379. package/workspace/useWorkspaceFiles.d.ts +13 -5
  380. package/workspace/useWorkspaceFiles.d.ts.map +1 -1
  381. package/workspace/useWorkspaceFiles.js +18 -19
  382. package/workspace/useWorkspaceFiles.js.map +1 -1
  383. package/workspace/workspaceListingCache.d.ts +47 -0
  384. package/workspace/workspaceListingCache.d.ts.map +1 -0
  385. package/workspace/workspaceListingCache.js +55 -0
  386. package/workspace/workspaceListingCache.js.map +1 -0
  387. package/session/inspector/ArtifactsTab.d.ts.map +0 -1
  388. package/session/inspector/ArtifactsTab.js.map +0 -1
  389. package/session/inspector/ChangesTab.d.ts.map +0 -1
  390. package/session/inspector/ChangesTab.js.map +0 -1
  391. package/session/inspector/InspectTab.d.ts.map +0 -1
  392. package/session/inspector/InspectTab.js.map +0 -1
  393. package/session/inspector/SessionInspector.d.ts +0 -64
  394. package/session/inspector/SessionInspector.d.ts.map +0 -1
  395. package/session/inspector/SessionInspector.js +0 -53
  396. package/session/inspector/SessionInspector.js.map +0 -1
  397. package/session/inspector/SetupTab.d.ts.map +0 -1
  398. package/session/inspector/SetupTab.js.map +0 -1
  399. package/session/inspector/UsageTab.d.ts.map +0 -1
  400. package/session/inspector/UsageTab.js.map +0 -1
  401. package/session/inspector/WorkspaceTab.d.ts +0 -35
  402. package/session/inspector/WorkspaceTab.d.ts.map +0 -1
  403. package/session/inspector/WorkspaceTab.js +0 -75
  404. package/session/inspector/WorkspaceTab.js.map +0 -1
  405. package/session/inspector/index.d.ts.map +0 -1
  406. package/session/inspector/index.js.map +0 -1
  407. package/session/inspector/useSessionInspector.d.ts +0 -49
  408. package/session/inspector/useSessionInspector.d.ts.map +0 -1
  409. package/session/inspector/useSessionInspector.js +0 -122
  410. package/session/inspector/useSessionInspector.js.map +0 -1
  411. package/src/session/inspector/SessionInspector.tsx +0 -162
  412. package/src/session/inspector/WorkspaceTab.tsx +0 -245
  413. package/src/session/inspector/__tests__/useSessionInspector.test.ts +0 -251
  414. package/src/session/inspector/useSessionInspector.ts +0 -178
  415. package/src/workspace/WorkspaceEntryFiles.tsx +0 -196
  416. package/workflow/motion-preference.d.ts.map +0 -1
  417. package/workflow/motion-preference.js.map +0 -1
  418. package/workspace/WorkspaceEntryFiles.d.ts +0 -22
  419. package/workspace/WorkspaceEntryFiles.d.ts.map +0 -1
  420. package/workspace/WorkspaceEntryFiles.js +0 -58
  421. package/workspace/WorkspaceEntryFiles.js.map +0 -1
  422. /package/session/{inspector → facets}/InspectTab.d.ts +0 -0
  423. /package/session/{inspector → facets}/InspectTab.js +0 -0
  424. /package/src/{workflow → internal}/__tests__/motion-preference.test.ts +0 -0
  425. /package/src/session/{inspector → facets}/InspectTab.tsx +0 -0
  426. /package/src/session/{inspector → facets}/__tests__/ChangesTab.test.tsx +0 -0
@@ -72,6 +72,67 @@ describe("ResizableSplit", () => {
72
72
  });
73
73
  });
74
74
 
75
+ describe("collapsedPane", () => {
76
+ /** The pane wrapper div for a rendered probe. */
77
+ function paneOf(testId: string): HTMLElement {
78
+ return screen.getByTestId(testId).parentElement as HTMLElement;
79
+ }
80
+
81
+ it("hides the collapsed pane and the separator; the sibling flexes", () => {
82
+ renderSplit({ collapsedPane: "secondary" });
83
+ expect(paneOf("secondary").className).toContain("hidden");
84
+ expect(paneOf("primary").className).toContain("flex-1");
85
+ const separator = screen.getByRole("separator", { hidden: true });
86
+ expect(separator.className).toContain("hidden");
87
+ expect(separator.getAttribute("tabindex")).toBe("-1");
88
+ });
89
+
90
+ it("overrides the resizable pane's fixed width while collapsed", () => {
91
+ renderSplit({ resizablePane: "primary", collapsedPane: "secondary" });
92
+ // Primary is normally pixel-sized; with its sibling collapsed it flexes.
93
+ expect(paneOf("primary").style.width).toBe("");
94
+ expect(paneOf("primary").className).toContain("flex-1");
95
+ });
96
+
97
+ it("keeps both children mounted (same DOM nodes) across a collapse toggle", () => {
98
+ const { rerender } = render(
99
+ <ResizableSplit
100
+ resizablePane="primary"
101
+ collapsedPane="secondary"
102
+ primary={<div data-testid="primary">Primary</div>}
103
+ secondary={<div data-testid="secondary">Secondary</div>}
104
+ />,
105
+ );
106
+ const primaryBefore = screen.getByTestId("primary");
107
+ const secondaryBefore = screen.getByTestId("secondary");
108
+ rerender(
109
+ <ResizableSplit
110
+ resizablePane="primary"
111
+ collapsedPane="none"
112
+ primary={<div data-testid="primary">Primary</div>}
113
+ secondary={<div data-testid="secondary">Secondary</div>}
114
+ />,
115
+ );
116
+ // Identical node references — expanding never remounted either pane.
117
+ expect(screen.getByTestId("primary")).toBe(primaryBefore);
118
+ expect(screen.getByTestId("secondary")).toBe(secondaryBefore);
119
+ expect(paneOf("primary").style.width).toBe("384px");
120
+ });
121
+
122
+ it("restores the persisted width when expanding after a collapse", () => {
123
+ localStorage.setItem("collapse-key", "451");
124
+ renderSplit({
125
+ resizablePane: "primary",
126
+ collapsedPane: "secondary",
127
+ storageKey: "collapse-key",
128
+ minSize: 300,
129
+ maxSize: 600,
130
+ });
131
+ const separator = screen.getByRole("separator", { hidden: true });
132
+ expect(separator.getAttribute("aria-valuenow")).toBe("451");
133
+ });
134
+ });
135
+
75
136
  describe("keyboard interaction", () => {
76
137
  it("increases panel width on ArrowLeft", () => {
77
138
  renderSplit({ defaultSize: 400 });
@@ -145,11 +206,72 @@ describe("ResizableSplit", () => {
145
206
  expect(separator.getAttribute("tabindex")).toBe("0");
146
207
  });
147
208
 
148
- it("has accessible label", () => {
209
+ it("has a default accessible label", () => {
149
210
  renderSplit();
150
211
  const separator = screen.getByRole("separator");
151
- expect(separator.getAttribute("aria-label")).toBe(
152
- "Resize inspector panel",
212
+ expect(separator.getAttribute("aria-label")).toBe("Resize panel");
213
+ });
214
+
215
+ it("uses a custom accessible label", () => {
216
+ renderSplit({ ariaLabel: "Resize file explorer" });
217
+ const separator = screen.getByRole("separator");
218
+ expect(separator.getAttribute("aria-label")).toBe("Resize file explorer");
219
+ });
220
+ });
221
+
222
+ describe("resizablePane", () => {
223
+ it("grows a primary pane on ArrowRight (toward its own side)", () => {
224
+ renderSplit({ resizablePane: "primary", defaultSize: 400 });
225
+ const separator = screen.getByRole("separator");
226
+
227
+ fireEvent.keyDown(separator, { key: "ArrowRight" });
228
+
229
+ expect(separator.getAttribute("aria-valuenow")).toBe("420");
230
+ });
231
+
232
+ it("shrinks a primary pane on ArrowLeft", () => {
233
+ renderSplit({ resizablePane: "primary", defaultSize: 400 });
234
+ const separator = screen.getByRole("separator");
235
+
236
+ fireEvent.keyDown(separator, { key: "ArrowLeft" });
237
+
238
+ expect(separator.getAttribute("aria-valuenow")).toBe("380");
239
+ });
240
+ });
241
+
242
+ describe("width re-initialization on key change", () => {
243
+ it("reloads the width from the new storageKey without remounting", () => {
244
+ localStorage.setItem("key-a", "300");
245
+ localStorage.setItem("key-b", "520");
246
+ const { rerender } = render(
247
+ <ResizableSplit
248
+ primary={<div data-testid="primary">Primary</div>}
249
+ secondary={<div data-testid="secondary">Secondary</div>}
250
+ storageKey="key-a"
251
+ minSize={200}
252
+ maxSize={600}
253
+ />,
254
+ );
255
+ const primary = screen.getByTestId("primary");
256
+ expect(screen.getByRole("separator").getAttribute("aria-valuenow")).toBe(
257
+ "300",
258
+ );
259
+
260
+ rerender(
261
+ <ResizableSplit
262
+ primary={<div data-testid="primary">Primary</div>}
263
+ secondary={<div data-testid="secondary">Secondary</div>}
264
+ storageKey="key-b"
265
+ resizablePane="primary"
266
+ minSize={200}
267
+ maxSize={600}
268
+ />,
269
+ );
270
+
271
+ // Same DOM node — the child was re-flowed, not remounted.
272
+ expect(screen.getByTestId("primary")).toBe(primary);
273
+ expect(screen.getByRole("separator").getAttribute("aria-valuenow")).toBe(
274
+ "520",
153
275
  );
154
276
  });
155
277
  });
@@ -3,6 +3,7 @@ import { render, cleanup } from "@testing-library/react";
3
3
  import type { ComponentType, ReactNode } from "react";
4
4
  import {
5
5
  MARKDOWN_COMPONENTS,
6
+ extractLeadingH1,
6
7
  unwrapEnclosingMarkdownFence,
7
8
  } from "../markdown-components";
8
9
 
@@ -63,6 +64,73 @@ describe("unwrapEnclosingMarkdownFence", () => {
63
64
  const once = unwrapEnclosingMarkdownFence(wrapped);
64
65
  expect(unwrapEnclosingMarkdownFence(once)).toBe(once);
65
66
  });
67
+
68
+ describe("allowBareFence (plan-document surfaces)", () => {
69
+ it.each([
70
+ ["a bare whole-body fence", "```\n# Plan\n- step\n```", "# Plan\n- step"],
71
+ [
72
+ "a bare fence with surrounding whitespace",
73
+ "\n```\n# Plan\n```\n",
74
+ "# Plan",
75
+ ],
76
+ [
77
+ "still unwraps the tagged form",
78
+ "```markdown\n# Plan\n```",
79
+ "# Plan",
80
+ ],
81
+ ])("unwraps %s", (_label, input, expected) => {
82
+ expect(unwrapEnclosingMarkdownFence(input, true)).toBe(expected);
83
+ });
84
+
85
+ it.each([
86
+ ["a language-tagged code block", "```python\nprint('hi')\n```"],
87
+ ["an unclosed bare fence (mid-stream)", "```\n# Plan in progr"],
88
+ ["trailing content after the fence", "```\n# Plan\n```\nthen more"],
89
+ ["leading content before the fence", "intro\n```\n# Plan\n```"],
90
+ ["plain markdown (no fence)", "# Plan\n\n- step"],
91
+ ])("leaves %s untouched even with allowBareFence", (_label, input) => {
92
+ expect(unwrapEnclosingMarkdownFence(input, true)).toBe(input);
93
+ });
94
+ });
95
+ });
96
+
97
+ describe("extractLeadingH1", () => {
98
+ it("splits a leading # heading into title and body", () => {
99
+ expect(extractLeadingH1("# The Plan\n\nFirst paragraph.")).toEqual({
100
+ title: "The Plan",
101
+ body: "First paragraph.",
102
+ });
103
+ });
104
+
105
+ it("handles a document that is only a heading", () => {
106
+ expect(extractLeadingH1("# Just a Title")).toEqual({
107
+ title: "Just a Title",
108
+ body: "",
109
+ });
110
+ });
111
+
112
+ it("ignores leading whitespace before the heading", () => {
113
+ expect(extractLeadingH1("\n\n# The Plan\nBody")).toEqual({
114
+ title: "The Plan",
115
+ body: "Body",
116
+ });
117
+ });
118
+
119
+ it("returns null title when the document does not open with an H1", () => {
120
+ const doc = "Intro paragraph.\n\n# Later Heading\nBody";
121
+ expect(extractLeadingH1(doc)).toEqual({ title: null, body: doc });
122
+ });
123
+
124
+ it("does not treat deeper headings as the title", () => {
125
+ const doc = "## Section\nBody";
126
+ expect(extractLeadingH1(doc)).toEqual({ title: null, body: doc });
127
+ });
128
+
129
+ it("trims trailing whitespace from the title", () => {
130
+ expect(extractLeadingH1("# Spaced Title \nBody").title).toBe(
131
+ "Spaced Title",
132
+ );
133
+ });
66
134
  });
67
135
 
68
136
  describe("MARKDOWN_COMPONENTS.code (shared highlight seam)", () => {
@@ -0,0 +1,97 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { render, cleanup } from "@testing-library/react";
3
+ import { useRevealLine, type RevealTarget } from "../useRevealLine";
4
+ import { prefersReducedMotion } from "../motion-preference";
5
+
6
+ // Mock the motion utility directly: it caches at module scope, so driving it
7
+ // through `matchMedia` would leak state across cases. Mocking gives each test a
8
+ // deterministic reduced-motion answer.
9
+ vi.mock("../motion-preference", () => ({
10
+ prefersReducedMotion: vi.fn(() => false),
11
+ }));
12
+
13
+ // A tiny harness that renders addressable line rows and drives the hook, so we
14
+ // exercise the real `[data-line]` query + scroll effect against a real DOM.
15
+ function LineView({
16
+ reveal,
17
+ lineCount = 5,
18
+ }: {
19
+ readonly reveal: RevealTarget | undefined;
20
+ readonly lineCount?: number;
21
+ }) {
22
+ const { containerRef, isRevealed } = useRevealLine<HTMLDivElement>(reveal);
23
+ return (
24
+ <div ref={containerRef}>
25
+ {Array.from({ length: lineCount }, (_, i) => {
26
+ const line = i + 1;
27
+ return (
28
+ <span key={line} data-line={line} data-revealed={isRevealed(line)}>
29
+ line {line}
30
+ </span>
31
+ );
32
+ })}
33
+ </div>
34
+ );
35
+ }
36
+
37
+ describe("useRevealLine", () => {
38
+ let scrollSpy: ReturnType<typeof vi.fn>;
39
+
40
+ beforeEach(() => {
41
+ vi.mocked(prefersReducedMotion).mockReturnValue(false);
42
+ // happy-dom has no real scrollIntoView — stub it to assert calls + options.
43
+ scrollSpy = vi.fn();
44
+ Element.prototype.scrollIntoView =
45
+ scrollSpy as unknown as typeof Element.prototype.scrollIntoView;
46
+ });
47
+
48
+ afterEach(() => {
49
+ cleanup();
50
+ });
51
+
52
+ it("scrolls the target line into view on mount", () => {
53
+ render(<LineView reveal={{ line: 3, nonce: 1 }} />);
54
+ expect(scrollSpy).toHaveBeenCalledTimes(1);
55
+ expect(scrollSpy).toHaveBeenCalledWith({ block: "center", behavior: "smooth" });
56
+ });
57
+
58
+ it("re-scrolls when the nonce changes, even for the same line", () => {
59
+ const { rerender } = render(<LineView reveal={{ line: 3, nonce: 1 }} />);
60
+ expect(scrollSpy).toHaveBeenCalledTimes(1);
61
+
62
+ rerender(<LineView reveal={{ line: 3, nonce: 2 }} />);
63
+ expect(scrollSpy).toHaveBeenCalledTimes(2);
64
+ });
65
+
66
+ it("does not re-scroll when the nonce is unchanged", () => {
67
+ const { rerender } = render(<LineView reveal={{ line: 3, nonce: 7 }} />);
68
+ expect(scrollSpy).toHaveBeenCalledTimes(1);
69
+
70
+ // Same nonce, different (irrelevant) prop → no additional scroll.
71
+ rerender(<LineView reveal={{ line: 3, nonce: 7 }} lineCount={6} />);
72
+ expect(scrollSpy).toHaveBeenCalledTimes(1);
73
+ });
74
+
75
+ it("is a no-op when the target line is out of range", () => {
76
+ render(<LineView reveal={{ line: 99, nonce: 1 }} lineCount={5} />);
77
+ expect(scrollSpy).not.toHaveBeenCalled();
78
+ });
79
+
80
+ it("is a no-op when there is no reveal target", () => {
81
+ render(<LineView reveal={undefined} />);
82
+ expect(scrollSpy).not.toHaveBeenCalled();
83
+ });
84
+
85
+ it("uses instant scroll under prefers-reduced-motion", () => {
86
+ vi.mocked(prefersReducedMotion).mockReturnValue(true);
87
+ render(<LineView reveal={{ line: 2, nonce: 1 }} />);
88
+ expect(scrollSpy).toHaveBeenCalledWith({ block: "center", behavior: "auto" });
89
+ });
90
+
91
+ it("reports the revealed line via isRevealed", () => {
92
+ const { container } = render(<LineView reveal={{ line: 4, nonce: 1 }} />);
93
+ const revealed = container.querySelector('[data-revealed="true"]');
94
+ expect(revealed?.getAttribute("data-line")).toBe("4");
95
+ expect(container.querySelectorAll('[data-revealed="true"]')).toHaveLength(1);
96
+ });
97
+ });
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { fileIconCategory } from "../index";
3
+
4
+ describe("fileIconCategory", () => {
5
+ it("classifies common code extensions", () => {
6
+ for (const name of ["a.ts", "b.tsx", "c.js", "m.py", "s.go", "r.rs"]) {
7
+ expect(fileIconCategory(name)).toBe("code");
8
+ }
9
+ });
10
+
11
+ it("classifies markup, style, data, markdown, image", () => {
12
+ expect(fileIconCategory("index.html")).toBe("markup");
13
+ expect(fileIconCategory("app.css")).toBe("style");
14
+ expect(fileIconCategory("tsconfig.json")).toBe("data");
15
+ expect(fileIconCategory("config.yaml")).toBe("data");
16
+ expect(fileIconCategory("README.md")).toBe("markdown");
17
+ expect(fileIconCategory("logo.png")).toBe("image");
18
+ });
19
+
20
+ it("is case-insensitive on the extension", () => {
21
+ expect(fileIconCategory("Main.TS")).toBe("code");
22
+ expect(fileIconCategory("PHOTO.PNG")).toBe("image");
23
+ });
24
+
25
+ it("falls back to generic for unknown or extension-less names", () => {
26
+ expect(fileIconCategory("Makefile")).toBe("generic");
27
+ expect(fileIconCategory("data.unknownext")).toBe("generic");
28
+ expect(fileIconCategory("trailingdot.")).toBe("generic");
29
+ });
30
+
31
+ it("uses the last dotted segment for multi-dot names", () => {
32
+ expect(fileIconCategory("app.test.ts")).toBe("code");
33
+ expect(fileIconCategory("archive.tar.gz")).toBe("generic");
34
+ });
35
+ });
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import type { ReactNode } from "react";
4
+
5
+ // Monochrome, shape-differentiated file icons. Deliberately NOT colored with
6
+ // language brand colors: DD-005 forbids hardcoded colors, and every glyph must
7
+ // tint from `currentColor` so it inherits the row's `--stgm-*` token in every
8
+ // preset and color mode (like VS Code's "Minimal" icon theme). Recognition
9
+ // comes from distinct shapes per broad category, not from color.
10
+
11
+ /** Broad file categories, chosen so one glyph serves many related extensions. */
12
+ export type FileIconCategory =
13
+ | "code"
14
+ | "markup"
15
+ | "style"
16
+ | "data"
17
+ | "markdown"
18
+ | "image"
19
+ | "generic";
20
+
21
+ /**
22
+ * Extension → category. Kept intentionally coarse: adding a language usually
23
+ * means adding one entry here, not a new glyph. Unknown extensions fall back to
24
+ * the generic document icon.
25
+ */
26
+ const EXTENSION_CATEGORY: Readonly<Record<string, FileIconCategory>> = {
27
+ // code
28
+ js: "code", jsx: "code", mjs: "code", cjs: "code",
29
+ ts: "code", tsx: "code",
30
+ py: "code", rb: "code", go: "code", rs: "code",
31
+ java: "code", kt: "code", c: "code", h: "code",
32
+ cpp: "code", cc: "code", hpp: "code", cs: "code",
33
+ php: "code", swift: "code", sh: "code", bash: "code", zsh: "code",
34
+ lua: "code", dart: "code", scala: "code", proto: "code",
35
+ // markup
36
+ html: "markup", htm: "markup", xml: "markup", vue: "markup", svelte: "markup",
37
+ // style
38
+ css: "style", scss: "style", sass: "style", less: "style",
39
+ // data / config
40
+ json: "data", jsonc: "data", yaml: "data", yml: "data",
41
+ toml: "data", ini: "data", env: "data", csv: "data", tsv: "data",
42
+ // markdown
43
+ md: "markdown", mdx: "markdown", markdown: "markdown",
44
+ // image
45
+ png: "image", jpg: "image", jpeg: "image", gif: "image",
46
+ webp: "image", svg: "image", ico: "image", bmp: "image",
47
+ };
48
+
49
+ /** Classify a file name into a {@link FileIconCategory} by its extension. */
50
+ export function fileIconCategory(fileName: string): FileIconCategory {
51
+ const dot = fileName.lastIndexOf(".");
52
+ if (dot < 0 || dot === fileName.length - 1) return "generic";
53
+ const ext = fileName.slice(dot + 1).toLowerCase();
54
+ return EXTENSION_CATEGORY[ext] ?? "generic";
55
+ }
56
+
57
+ /**
58
+ * A monochrome icon for a file, chosen by extension category. Tints from
59
+ * `currentColor`; size via the surrounding font/box.
60
+ */
61
+ export function FileTypeIcon({ fileName }: { readonly fileName: string }): ReactNode {
62
+ const category = fileIconCategory(fileName);
63
+ return (
64
+ <svg
65
+ width="14"
66
+ height="14"
67
+ viewBox="0 0 16 16"
68
+ fill="none"
69
+ stroke="currentColor"
70
+ strokeWidth="1.3"
71
+ strokeLinecap="round"
72
+ strokeLinejoin="round"
73
+ className="shrink-0"
74
+ aria-hidden="true"
75
+ >
76
+ {/* Every category shares a document outline for a consistent silhouette. */}
77
+ <path d="M9.5 1.5H4.5C3.95 1.5 3.5 1.95 3.5 2.5V13.5C3.5 14.05 3.95 14.5 4.5 14.5H11.5C12.05 14.5 12.5 14.05 12.5 13.5V4.5L9.5 1.5Z" />
78
+ <path d="M9.3 1.7V4.5H12.3" />
79
+ {CATEGORY_GLYPH[category]}
80
+ </svg>
81
+ );
82
+ }
83
+
84
+ /** The inner glyph that distinguishes each category, drawn in the doc body. */
85
+ const CATEGORY_GLYPH: Readonly<Record<FileIconCategory, ReactNode>> = {
86
+ code: (
87
+ <>
88
+ <path d="M7 8.5L5.75 9.75L7 11" />
89
+ <path d="M9 8.5L10.25 9.75L9 11" />
90
+ </>
91
+ ),
92
+ markup: (
93
+ <>
94
+ <path d="M6.5 8.5L5.5 9.9L6.5 11.3" />
95
+ <path d="M9.5 8.5L10.5 9.9L9.5 11.3" />
96
+ </>
97
+ ),
98
+ style: <path d="M5.5 9.3H10.5M5.5 11.1H8.5" />,
99
+ data: (
100
+ <>
101
+ <path d="M7 8.4C6.2 8.4 6.2 9.3 6.2 9.9C6.2 10.5 6.2 11.4 5.4 11.4" />
102
+ <path d="M9 8.4C9.8 8.4 9.8 9.3 9.8 9.9C9.8 10.5 9.8 11.4 10.6 11.4" />
103
+ </>
104
+ ),
105
+ markdown: <path d="M5.4 11.3V8.6L6.9 10.1L8.4 8.6V11.3M10.4 8.7V11.1M9.5 10.2L10.4 11.2L11.3 10.2" />,
106
+ image: (
107
+ <>
108
+ <circle cx="6.4" cy="9" r="0.7" />
109
+ <path d="M5.2 11.4L7.3 9.9L10.6 11.6" />
110
+ </>
111
+ ),
112
+ generic: <path d="M5.5 9.3H10.5M5.5 11.1H10.5" />,
113
+ };
114
+
115
+ /** A folder icon; `open` swaps to the opened-folder silhouette. */
116
+ export function FolderTypeIcon({ open = false }: { readonly open?: boolean }): ReactNode {
117
+ return (
118
+ <svg
119
+ width="14"
120
+ height="14"
121
+ viewBox="0 0 16 16"
122
+ fill="none"
123
+ stroke="currentColor"
124
+ strokeWidth="1.3"
125
+ strokeLinecap="round"
126
+ strokeLinejoin="round"
127
+ className="shrink-0"
128
+ aria-hidden="true"
129
+ >
130
+ {open ? (
131
+ <path d="M1.8 5.5V12A1 1 0 002.8 13H12.6L14.5 7.5H4.2L2.8 11" />
132
+ ) : (
133
+ <path d="M1.8 4.5A1 1 0 012.8 3.5H6L7.3 5H13.2A1 1 0 0114.2 6V12A1 1 0 0113.2 13H2.8A1 1 0 011.8 12V4.5Z" />
134
+ )}
135
+ </svg>
136
+ );
137
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useCallback, useState, type DragEvent } from "react";
4
4
  import { cn } from "@stigmer/theme";
5
+ import { FileTypeIcon, FolderTypeIcon } from "../file-icons/index.js";
5
6
  import type { TreeNode } from "./tree-node.js";
6
7
 
7
8
  /**
@@ -15,6 +16,12 @@ export interface FileTreeNodeProps {
15
16
  readonly node: TreeNode;
16
17
  readonly selectedPath: string;
17
18
  readonly onSelect: (path: string) => void;
19
+ /**
20
+ * Optional double-click handler for file nodes. When set, a double-click on a
21
+ * file calls `onActivate(path)` (used by the workspace surface to pin a
22
+ * previewed tab); the single click still fires `onSelect`. Folders ignore it.
23
+ */
24
+ readonly onActivate?: (path: string) => void;
18
25
  readonly depth: number;
19
26
  /**
20
27
  * When `true`, file nodes (not folders) become draggable and emit
@@ -24,6 +31,17 @@ export interface FileTreeNodeProps {
24
31
  * where file referencing is not applicable (e.g., SkillFileBrowser).
25
32
  */
26
33
  readonly enableDrag?: boolean;
34
+ /**
35
+ * When `true`, render monochrome file-type / folder icons before each name
36
+ * (VS Code explorer feel). Opt-in so drag/skill contexts stay icon-free.
37
+ */
38
+ readonly showIcons?: boolean;
39
+ /**
40
+ * When `true`, draw vertical indent guides down each nested group and indent
41
+ * via nested margins rather than per-row left padding. Opt-in; the default
42
+ * padding-based indentation is preserved for existing consumers.
43
+ */
44
+ readonly indentGuides?: boolean;
27
45
  /**
28
46
  * Maximum depth at which folders start collapsed instead of expanded.
29
47
  * Folders at depth >= maxInitialDepth render collapsed by default,
@@ -49,8 +67,11 @@ export function FileTreeNode({
49
67
  node,
50
68
  selectedPath,
51
69
  onSelect,
70
+ onActivate,
52
71
  depth,
53
72
  enableDrag = false,
73
+ showIcons = false,
74
+ indentGuides = false,
54
75
  maxInitialDepth,
55
76
  }: FileTreeNodeProps) {
56
77
  const [expanded, setExpanded] = useState(
@@ -68,6 +89,10 @@ export function FileTreeNode({
68
89
  }
69
90
  }, [isFolder, node.path, onSelect]);
70
91
 
92
+ const handleDoubleClick = useCallback(() => {
93
+ if (!isFolder) onActivate?.(node.path);
94
+ }, [isFolder, node.path, onActivate]);
95
+
71
96
  const handleDragStart = useCallback(
72
97
  (e: DragEvent) => {
73
98
  e.dataTransfer.setData(FILE_REF_MIME, JSON.stringify({ path: node.path }));
@@ -81,17 +106,20 @@ export function FileTreeNode({
81
106
  <button
82
107
  type="button"
83
108
  onClick={handleClick}
109
+ onDoubleClick={onActivate ? handleDoubleClick : undefined}
84
110
  draggable={isDraggable}
85
111
  onDragStart={isDraggable ? handleDragStart : undefined}
86
112
  className={cn(
87
- "flex w-full items-center gap-1.5 px-3 py-1 text-left text-xs transition-colors",
113
+ "flex w-full items-center gap-1.5 py-1 text-left text-xs transition-colors",
88
114
  "hover:bg-muted",
89
115
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
90
116
  isSelected && !isFolder && "bg-muted text-foreground font-medium",
91
117
  !isSelected && "text-muted-foreground",
92
118
  isDraggable && "cursor-grab active:cursor-grabbing",
93
119
  )}
94
- style={{ paddingLeft: `${12 + depth * 12}px` }}
120
+ // Indent-guide mode indents via the nested group's margin, so rows need
121
+ // only a small constant inset; padding mode keeps the per-depth inset.
122
+ style={{ paddingLeft: indentGuides ? "8px" : `${12 + depth * 12}px`, paddingRight: "8px" }}
95
123
  aria-current={isSelected ? "true" : undefined}
96
124
  >
97
125
  {isFolder && (
@@ -99,20 +127,32 @@ export function FileTreeNode({
99
127
  {expanded ? "▼" : "▶"}
100
128
  </span>
101
129
  )}
130
+ {showIcons &&
131
+ (isFolder ? (
132
+ <FolderTypeIcon open={expanded} />
133
+ ) : (
134
+ <FileTypeIcon fileName={node.name} />
135
+ ))}
102
136
  <span className={cn("truncate", isFolder && "font-medium text-foreground")}>
103
137
  {node.name}
104
138
  </span>
105
139
  </button>
106
140
  {isFolder && expanded && node.children && (
107
- <ul role="group">
141
+ <ul
142
+ role="group"
143
+ className={cn(indentGuides && "ml-3 border-l border-border")}
144
+ >
108
145
  {node.children.map((child) => (
109
146
  <FileTreeNode
110
147
  key={child.path}
111
148
  node={child}
112
149
  selectedPath={selectedPath}
113
150
  onSelect={onSelect}
151
+ onActivate={onActivate}
114
152
  depth={depth + 1}
115
153
  enableDrag={enableDrag}
154
+ showIcons={showIcons}
155
+ indentGuides={indentGuides}
116
156
  maxInitialDepth={maxInitialDepth}
117
157
  />
118
158
  ))}